#include "qwsvdef.h" #define Q2NUM_FOR_EDICT(ent) (((char *)ent - (char *)ge->edicts) / ge->edict_size) #ifndef Q2SERVER qboolean SVQ2_InitGameProgs(void) { return false; } #else game_export_t *ge; int SVQ2_AreaEdicts (vec3_t mins, vec3_t maxs, q2edict_t **list, int maxcount, int areatype); void SVQ2_LinkEdict(q2edict_t *ent); void SVQ2_UnlinkEdict(q2edict_t *ent); trace_t SVQ2_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, q2edict_t *passedict); void Sys_UnloadGame (void); void *Sys_GetGameAPI (void *parms); /* =============== PF_Unicast Sends the contents of the mutlicast buffer to a single client =============== */ static void PFQ2_Unicast (q2edict_t *ent, qboolean reliable) { int p; client_t *client; if (!ent) return; p = Q2NUM_FOR_EDICT(ent); if (p < 1 || p > MAX_CLIENTS) return; client = svs.clients + (p-1); if (client->state < cs_connected) return; if (reliable) SZ_Write (&client->netchan.message, sv.multicast.data, sv.multicast.cursize); else SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize); SZ_Clear (&sv.multicast); } /* =============== PF_dprintf Debug print to server console =============== */ static void VARGS PFQ2_dprintf (char *fmt, ...) { char msg[1024]; va_list argptr; va_start (argptr,fmt); vsprintf (msg, fmt, argptr); va_end (argptr); Con_Printf ("%s", msg); } /* =============== PF_cprintf Print to a single client =============== */ static void VARGS PFQ2_cprintf (q2edict_t *ent, int level, char *fmt, ...) { char msg[1024]; va_list argptr; int n=0; if (ent) { n = Q2NUM_FOR_EDICT(ent); if (n < 1 || n > MAX_CLIENTS) { Sys_Error ("cprintf to a non-client"); return; } if (svs.clients[n-1].state < cs_connected) { Sys_Error ("cprintf to a disconnected client"); return; } } va_start (argptr,fmt); vsprintf (msg, fmt, argptr); va_end (argptr); if (ent) SV_ClientPrintf (svs.clients+(n-1), level, "%s", msg); else Con_Printf ("%s", msg); } /* =============== PF_centerprintf centerprint to a single client =============== */ static void VARGS PFQ2_centerprintf (q2edict_t *ent, char *fmt, ...) { char msg[1024]; va_list argptr; int n; n = Q2NUM_FOR_EDICT(ent); if (n < 1 || n > MAX_CLIENTS) return; // Com_Error (ERR_DROP, "centerprintf to a non-client"); if (svs.clients[n-1].state < cs_connected) return; va_start (argptr,fmt); vsprintf (msg, fmt, argptr); va_end (argptr); MSG_WriteByte (&sv.multicast,svcq2_centerprint); MSG_WriteString (&sv.multicast,msg); PFQ2_Unicast (ent, true); } /* =============== PF_error Abort the server with a game error =============== */ static void VARGS PFQ2_error (char *fmt, ...) { char msg[1024]; va_list argptr; va_start (argptr,fmt); vsprintf (msg, fmt, argptr); va_end (argptr); SV_Error("Game Error: %s", msg); } /* =============== PF_Configstring =============== */ static void PFQ2_Configstring (int i, char *val) { int j; if (i < 0 || i >= Q2MAX_CONFIGSTRINGS) Sys_Error ("configstring: bad index %i\n", i); if (!val) val = ""; //work out range if (i >= Q2CS_LIGHTS && i < Q2CS_LIGHTS+Q2MAX_LIGHTSTYLES) { j = i - Q2CS_LIGHTS; if (j < MAX_LIGHTSTYLES) { if (sv.lightstyles[j]) Z_Free(sv.lightstyles[j]); sv.lightstyles[j] = Z_Malloc(strlen(val)+1); strcpy(sv.lightstyles[j], val); } } else if (i >= Q2CS_MODELS && i < Q2CS_MODELS+Q2MAX_MODELS) { Q_strncpyS(sv.model_precache[i-Q2CS_MODELS], val, MAX_QPATH-1); } else if (i >= Q2CS_SOUNDS && i < Q2CS_SOUNDS+Q2MAX_SOUNDS) { Q_strncpyS(sv.sound_precache[i-Q2CS_SOUNDS], val, MAX_QPATH-1); } else if (i >= Q2CS_IMAGES && i < Q2CS_IMAGES+Q2MAX_IMAGES) { Q_strncpyS(sv.image_precache[i-Q2CS_IMAGES], val, MAX_QPATH-1); } else if (i == Q2CS_STATUSBAR) { if (sv.statusbar) Z_Free(sv.statusbar); sv.statusbar = Z_Malloc(strlen(val)+1); strcpy(sv.statusbar, val); } else if (i == Q2CS_NAME) { Q_strncpyz(sv.mapname, val, sizeof(sv.name)); } else { /* #define Q2CS_NAME 0 #define Q2CS_CDTRACK 1 #define Q2CS_SKY 2 #define Q2CS_SKYAXIS 3 // %f %f %f format #define Q2CS_SKYROTATE 4 #define Q2CS_STATUSBAR 5 // display program string #define Q2CS_AIRACCEL 29 // air acceleration control #define Q2CS_MAXCLIENTS 30 #define Q2CS_MAPCHECKSUM 31 // for catching cheater maps #define Q2CS_MODELS 32 #define Q2CS_SOUNDS (Q2CS_MODELS +Q2MAX_MODELS) #define Q2CS_IMAGES (Q2CS_SOUNDS +Q2MAX_SOUNDS) #define Q2CS_LIGHTS (Q2CS_IMAGES +Q2MAX_IMAGES) #define Q2CS_ITEMS (Q2CS_LIGHTS +Q2MAX_LIGHTSTYLES) #define Q2CS_PLAYERSKINS (Q2CS_ITEMS +Q2MAX_ITEMS) #define Q2CS_GENERAL (Q2CS_PLAYERSKINS +Q2MAX_CLIENTS) */ Con_Printf("Ignoring configstring %i\n", i); } if (sv.state != ss_loading) { // send the update to everyone SZ_Clear (&sv.multicast); MSG_WriteChar (&sv.multicast, svcq2_configstring); MSG_WriteShort (&sv.multicast, i); MSG_WriteString (&sv.multicast, val); SV_Multicast (vec3_origin, MULTICAST_ALL_R); } } static int SVQ2_FindIndex (char *name, int start, int max, char *strings, int stringlength, qboolean create) { int i; if (!name || !name[0]) return 0; for (i=1 ; imodel = name; ent->s.modelindex = i; // if it is an inline model, get the size information for it if (name[0] == '*') { mod = Mod_FindName (name); VectorCopy (mod->mins, ent->mins); VectorCopy (mod->maxs, ent->maxs); SVQ2_LinkEdict (ent); } } /* static qboolean PFQ2_Q1BSP_AreasConnected (int area1, int area2) { return true; } static qboolean CMQ2_Q1BSP_SetAreaPortalState (int portalnum, qboolean open) { return true; }*/ static void PFQ2_WriteChar (int c) {MSG_WriteChar (&sv.multicast, c);} static void PFQ2_WriteByte (int c) {MSG_WriteByte (&sv.multicast, c);} static void PFQ2_WriteShort (int c) {MSG_WriteShort (&sv.multicast, c);} static void PFQ2_WriteLong (int c) {MSG_WriteLong (&sv.multicast, c);} static void PFQ2_WriteFloat (float f) {MSG_WriteFloat (&sv.multicast, f);} static void PFQ2_WriteString (char *s) {MSG_WriteString (&sv.multicast, s);} static void PFQ2_WriteAngle (float f) {MSG_WriteAngle (&sv.multicast, f);} static void PFQ2_WritePos (vec3_t pos) { MSG_WriteCoord (&sv.multicast, pos[0]); MSG_WriteCoord (&sv.multicast, pos[1]); MSG_WriteCoord (&sv.multicast, pos[2]); } static void PFQ2_WriteDir (vec3_t dir) {MSG_WriteDir (&sv.multicast, dir);} /* ================= PF_inPVS Also checks portalareas so that doors block sight ================= */ static qboolean PFQ2_inPVS (vec3_t p1, vec3_t p2) { int leafnum; int cluster; int area1, area2; qbyte *mask; leafnum = CM_PointLeafnum (p1); cluster = CM_LeafCluster (leafnum); area1 = CM_LeafArea (leafnum); mask = CM_ClusterPVS (cluster, NULL); leafnum = CM_PointLeafnum (p2); cluster = CM_LeafCluster (leafnum); area2 = CM_LeafArea (leafnum); if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) return false; if (!CM_AreasConnected (area1, area2)) return false; // a door blocks sight return true; } /* ================= PF_inPHS Also checks portalareas so that doors block sound ================= */ static qboolean PFQ2_inPHS (vec3_t p1, vec3_t p2) { int leafnum; int cluster; int area1, area2; qbyte *mask; leafnum = CM_PointLeafnum (p1); cluster = CM_LeafCluster (leafnum); area1 = CM_LeafArea (leafnum); mask = CM_ClusterPHS (cluster); leafnum = CM_PointLeafnum (p2); cluster = CM_LeafCluster (leafnum); area2 = CM_LeafArea (leafnum); if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) return false; // more than one bounce away if (!CM_AreasConnected (area1, area2)) return false; // a door blocks hearing return true; } #define Q2SND_VOLUME (1<<0) // a byte #define Q2SND_ATTENUATION (1<<1) // a byte #define Q2SND_POS (1<<2) // three coordinates #define Q2SND_ENT (1<<3) // a short 0-2: channel, 3-12: entity #define Q2SND_OFFSET (1<<4) // a byte, msec offset from frame start #define Q2DEFAULT_SOUND_PACKET_VOLUME 1.0 #define Q2DEFAULT_SOUND_PACKET_ATTENUATION 1.0 #define Q2ATTN_NONE 0 // full volume the entire level #define Q2ATTN_NORM 1/* #define Q2CHAN_AUTO 0 #define Q2CHAN_WEAPON 1 #define Q2CHAN_VOICE 2 #define Q2CHAN_ITEM 3 #define Q2CHAN_BODY 4*/ #define Q2CHAN_RELIABLE 16 void SVQ2_StartSound (vec3_t origin, q2edict_t *entity, int channel, int soundindex, float volume, float attenuation, float timeofs) { int sendchan; int flags; int i; int ent; vec3_t origin_v; qboolean use_phs; if (volume < 0 || volume > 1.0) Sys_Error ("SV_StartSound: volume = %f", volume); if (attenuation < 0 || attenuation > 4) Sys_Error ("SV_StartSound: attenuation = %f", attenuation); // if (channel < 0 || channel > 15) // Sys_Error ("SV_StartSound: channel = %i", channel); if (timeofs < 0 || timeofs > 0.255) Sys_Error ("SV_StartSound: timeofs = %f", timeofs); ent = Q2NUM_FOR_EDICT(entity); if (channel & 8) // no PHS flag { use_phs = false; channel &= 7; } else use_phs = true; sendchan = (ent<<3) | (channel&7); flags = 0; if (volume != Q2DEFAULT_SOUND_PACKET_VOLUME) flags |= Q2SND_VOLUME; if (attenuation != Q2DEFAULT_SOUND_PACKET_ATTENUATION) flags |= Q2SND_ATTENUATION; // the client doesn't know that bmodels have weird origins // the origin can also be explicitly set if ( (entity->svflags & SVF_NOCLIENT) || (entity->solid == Q2SOLID_BSP) || origin ) flags |= Q2SND_POS; // always send the entity number for channel overrides flags |= Q2SND_ENT; if (timeofs) flags |= Q2SND_OFFSET; // use the entity origin unless it is a bmodel or explicitly specified if (!origin) { origin = origin_v; if (entity->solid == Q2SOLID_BSP) { for (i=0 ; i<3 ; i++) origin_v[i] = entity->s.origin[i]+0.5*(entity->mins[i]+entity->maxs[i]); } else { VectorCopy (entity->s.origin, origin_v); } } MSG_WriteByte (&sv.multicast, svcq2_sound); MSG_WriteByte (&sv.multicast, flags); MSG_WriteByte (&sv.multicast, soundindex); if (flags & Q2SND_VOLUME) MSG_WriteByte (&sv.multicast, volume*255); if (flags & Q2SND_ATTENUATION) MSG_WriteByte (&sv.multicast, attenuation*64); if (flags & Q2SND_OFFSET) MSG_WriteByte (&sv.multicast, timeofs*1000); if (flags & Q2SND_ENT) MSG_WriteShort (&sv.multicast, sendchan); if (flags & Q2SND_POS) { MSG_WriteCoord (&sv.multicast, origin[0]); MSG_WriteCoord (&sv.multicast, origin[1]); MSG_WriteCoord (&sv.multicast, origin[2]); } // if the sound doesn't attenuate,send it to everyone // (global radio chatter, voiceovers, etc) if (attenuation == Q2ATTN_NONE) use_phs = false; if (channel & Q2CHAN_RELIABLE) { if (use_phs) SV_Multicast (origin, MULTICAST_PHS_R); else SV_Multicast (origin, MULTICAST_ALL_R); } else { if (use_phs) SV_Multicast (origin, MULTICAST_PHS); else SV_Multicast (origin, MULTICAST_ALL); } } static void PFQ2_StartSound (q2edict_t *entity, int channel, int sound_num, float volume, float attenuation, float timeofs) { if (!entity) return; SVQ2_StartSound (NULL, entity, channel, sound_num, volume, attenuation, timeofs); } static q2trace_t SVQ2_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, q2edict_t *passedict, int contentmask) { q2trace_t ret; trace_t tr; static vec3_t nullvec; if (!mins) mins = nullvec; if (!maxs) maxs = nullvec; tr = SVQ2_Move(start, mins, maxs, end, contentmask, passedict); memcpy(&ret, &tr, sizeof(q2trace_t)); return ret; } static int SVQ2_PointContents (vec3_t p) { q2trace_t tr = SVQ2_Trace(p, NULL, NULL, p, NULL, ~0); return tr.contents; // return CM_PointContents(p, 0); } static cvar_t *Q2Cvar_Get (char *var_name, char *value, int flags) { return Cvar_Get(var_name, value, flags, "Quake2 game variables"); } cvar_t *Q2Cvar_Set (char *var_name, char *value) { cvar_t *var = Cvar_FindVar(var_name); if (!var) { Con_Printf("Q2Cvar_Set: variable %s not found\n", var_name); return NULL; } return Cvar_Set(var, value); } cvar_t *Q2Cvar_ForceSet (char *var_name, char *value) { cvar_t *var = Cvar_FindVar(var_name); if (!var) { Con_Printf("Q2Cvar_Set: variable %s not found\n", var); return NULL; } return Cvar_ForceSet(var, value); } //============================================== /* =============== SV_ShutdownGameProgs Called when either the entire server is being killed, or it is changing to a different game directory. =============== */ void SVQ2_ShutdownGameProgs (void) { if (!ge) return; ge->Shutdown (); Sys_UnloadGame (); ge = NULL; } static void AddCommandString(char *command) { Cbuf_AddText(command, RESTRICT_LOCAL); } /* =============== SV_InitGameProgs Init the game subsystem for a new map =============== */ void Q2SCR_DebugGraph(float value, int color) {return;} void Q2_Pmove (q2pmove_t *pmove); qboolean SVQ2_InitGameProgs(void) { static game_import_t import; if (COM_CheckParm("-noq2dll")) { SVQ2_ShutdownGameProgs(); return false; } // unload anything we have now if (sv.worldmodel->fromgame == fg_quake || sv.worldmodel->fromgame == fg_halflife) //we don't support q1 or hl maps yet... If ever. { SVQ2_ShutdownGameProgs(); return false; } if (ge) return true; // calc the imports. import.multicast = SV_Multicast; import.unicast = PFQ2_Unicast; import.bprintf = SV_BroadcastPrintf; import.dprintf = PFQ2_dprintf; import.cprintf = PFQ2_cprintf; import.centerprintf = PFQ2_centerprintf; import.error = PFQ2_error; import.linkentity = SVQ2_LinkEdict; import.unlinkentity = SVQ2_UnlinkEdict; import.BoxEdicts = SVQ2_AreaEdicts; import.trace = SVQ2_Trace; import.pointcontents = SVQ2_PointContents; import.setmodel = PFQ2_setmodel; import.inPVS = PFQ2_inPVS; import.inPHS = PFQ2_inPHS; import.Pmove = Q2_Pmove; import.modelindex = SVQ2_ModelIndex; import.soundindex = SVQ2_SoundIndex; import.imageindex = SVQ2_ImageIndex; import.configstring = PFQ2_Configstring; import.sound = PFQ2_StartSound; import.positioned_sound = SVQ2_StartSound; import.WriteChar = PFQ2_WriteChar; import.WriteByte = PFQ2_WriteByte; import.WriteShort = PFQ2_WriteShort; import.WriteLong = PFQ2_WriteLong; import.WriteFloat = PFQ2_WriteFloat; import.WriteString = PFQ2_WriteString; import.WritePosition = PFQ2_WritePos; import.WriteDir = PFQ2_WriteDir; import.WriteAngle = PFQ2_WriteAngle; import.TagMalloc = Z_TagMalloc; import.TagFree = Z_Free; import.FreeTags = Z_FreeTags; import.cvar = Q2Cvar_Get; import.cvar_set = Q2Cvar_Set; import.cvar_forceset = Q2Cvar_ForceSet; import.argc = Cmd_Argc; import.argv = Cmd_Argv; import.args = Cmd_Args; import.AddCommandString = AddCommandString; import.DebugGraph = Q2SCR_DebugGraph; import.SetAreaPortalState = CMQ2_SetAreaPortalState; import.AreasConnected = CM_AreasConnected; if (sv.worldmodel->fromgame == fg_quake || sv.worldmodel->fromgame == fg_halflife) { return false; /* import.linkentity = SVQ2_Q1BSP_LinkEdict; import.unlinkentity = SVQ2_Q1BSP_UnlinkEdict; import.BoxEdicts = SVQ2_Q1BSP_AreaEdicts; import.trace = SVQ2_Q1BSP_Trace; import.pointcontents = SVQ2_Q1BSP_PointContents; import.setmodel = PFQ2_Q1BSP_setmodel; import.inPVS = PFQ2_Q1BSP_inPVS; import.inPHS = PFQ2_Q1BSP_inPHS; import.Pmove = Q2_Pmove; import.AreasConnected = PFQ2_Q1BSP_AreasConnected; import.SetAreaPortalState = CMQ2_Q1BSP_SetAreaPortalState; */ } ge = (game_export_t *)Sys_GetGameAPI (&import); if (!ge) return false; if (ge->apiversion != GAME_API_VERSION) { Con_Printf("game is version %i, not %i", ge->apiversion, GAME_API_VERSION); SVQ2_ShutdownGameProgs(); return false; } ge->Init (); return true; } #endif