/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2010-2014 QuakeSpasm developers 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" server_t sv; server_static_t svs; static char localmodels[MAX_MODELS][8]; // inline model names for precache int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz extern qboolean pr_alpha_supported; //johnfitz //============================================================================ /* =============== SV_Protocol_f =============== */ void SV_Protocol_f (void) { int i; switch (Cmd_Argc()) { case 1: Con_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol); break; case 2: i = atoi(Cmd_Argv(1)); if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) Con_Printf ("sv_protocol must be %i or %i or %i\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); else { sv_protocol = i; if (sv.active) Con_Printf ("changes will not take effect until the next level load.\n"); } break; default: Con_SafePrintf ("usage: sv_protocol \n"); break; } } /* =============== SV_Init =============== */ void SV_Init (void) { int i; const char *p; extern cvar_t sv_maxvelocity; extern cvar_t sv_gravity; extern cvar_t sv_nostep; extern cvar_t sv_freezenonclients; extern cvar_t sv_friction; extern cvar_t sv_edgefriction; extern cvar_t sv_stopspeed; extern cvar_t sv_maxspeed; extern cvar_t sv_accelerate; extern cvar_t sv_idealpitchscale; extern cvar_t sv_aim; extern cvar_t sv_altnoclip; //johnfitz sv.edicts = NULL; // ericw -- sv.edicts switched to use malloc() Cvar_RegisterVariable (&sv_maxvelocity); Cvar_RegisterVariable (&sv_gravity); Cvar_RegisterVariable (&sv_friction); Cvar_SetCallback (&sv_gravity, Host_Callback_Notify); Cvar_SetCallback (&sv_friction, Host_Callback_Notify); Cvar_RegisterVariable (&sv_edgefriction); Cvar_RegisterVariable (&sv_stopspeed); Cvar_RegisterVariable (&sv_maxspeed); Cvar_SetCallback (&sv_maxspeed, Host_Callback_Notify); Cvar_RegisterVariable (&sv_accelerate); Cvar_RegisterVariable (&sv_idealpitchscale); Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_freezenonclients); Cvar_RegisterVariable (&sv_altnoclip); //johnfitz Cmd_AddCommand ("sv_protocol", &SV_Protocol_f); //johnfitz for (i=0 ; i MAX_DATAGRAM-16) return; MSG_WriteByte (&sv.datagram, svc_particle); MSG_WriteCoord (&sv.datagram, org[0], sv.protocolflags); MSG_WriteCoord (&sv.datagram, org[1], sv.protocolflags); MSG_WriteCoord (&sv.datagram, org[2], sv.protocolflags); for (i=0 ; i<3 ; i++) { v = dir[i]*16; if (v > 127) v = 127; else if (v < -128) v = -128; MSG_WriteChar (&sv.datagram, v); } MSG_WriteByte (&sv.datagram, count); MSG_WriteByte (&sv.datagram, color); } /* ================== 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 allready 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 (edict_t *entity, int channel, const char *sample, int volume, float attenuation) { int sound_num, ent; int i, field_mask; if (volume < 0 || volume > 255) Host_Error ("SV_StartSound: volume = %i", volume); if (attenuation < 0 || attenuation > 4) Host_Error ("SV_StartSound: attenuation = %f", attenuation); if (channel < 0 || channel > 7) Host_Error ("SV_StartSound: channel = %i", channel); if (sv.datagram.cursize > MAX_DATAGRAM-16) return; // find precache number for sound for (sound_num = 1; sound_num < MAX_SOUNDS && sv.sound_precache[sound_num]; sound_num++) { if (!strcmp(sample, sv.sound_precache[sound_num])) break; } if (sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num]) { Con_Printf ("SV_StartSound: %s not precacheed\n", sample); return; } ent = NUM_FOR_EDICT(entity); field_mask = 0; if (volume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= SND_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) field_mask |= SND_ATTENUATION; //johnfitz -- PROTOCOL_FITZQUAKE if (ent >= 8192) { if (sv.protocol == PROTOCOL_NETQUAKE) return; //don't send any info protocol can't support else field_mask |= SND_LARGEENTITY; } if (sound_num >= 256 || channel >= 8) { if (sv.protocol == PROTOCOL_NETQUAKE) return; //don't send any info protocol can't support else field_mask |= SND_LARGESOUND; } //johnfitz // directed messages go only to the entity the are targeted on MSG_WriteByte (&sv.datagram, svc_sound); MSG_WriteByte (&sv.datagram, field_mask); if (field_mask & SND_VOLUME) MSG_WriteByte (&sv.datagram, volume); if (field_mask & SND_ATTENUATION) MSG_WriteByte (&sv.datagram, attenuation*64); //johnfitz -- PROTOCOL_FITZQUAKE if (field_mask & SND_LARGEENTITY) { MSG_WriteShort (&sv.datagram, ent); MSG_WriteByte (&sv.datagram, channel); } else MSG_WriteShort (&sv.datagram, (ent<<3) | channel); if (field_mask & SND_LARGESOUND) MSG_WriteShort (&sv.datagram, sound_num); else MSG_WriteByte (&sv.datagram, sound_num); //johnfitz for (i = 0; i < 3; i++) MSG_WriteCoord (&sv.datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); } /* ============================================================================== 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) { const char **s; char message[2048]; int i; //johnfitz MSG_WriteByte (&client->message, svc_print); sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version MSG_WriteString (&client->message,message); MSG_WriteByte (&client->message, svc_serverinfo); MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION if (sv.protocol == PROTOCOL_RMQ) { // mh - now send protocol flags so that the client knows the protocol features to expect MSG_WriteLong (&client->message, sv.protocolflags); } MSG_WriteByte (&client->message, svs.maxclients); if (!coop.value && deathmatch.value) MSG_WriteByte (&client->message, GAME_DEATHMATCH); else MSG_WriteByte (&client->message, GAME_COOP); MSG_WriteString (&client->message, PR_GetString(sv.edicts->v.message)); //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 for (i=0,s = sv.model_precache+1 ; *s; s++,i++) if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); for (i=0,s = sv.sound_precache+1 ; *s ; s++,i++) if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); //johnfitz // send music MSG_WriteByte (&client->message, svc_cdtrack); MSG_WriteByte (&client->message, sv.edicts->v.sounds); MSG_WriteByte (&client->message, sv.edicts->v.sounds); // set view MSG_WriteByte (&client->message, svc_setview); MSG_WriteShort (&client->message, NUM_FOR_EDICT(client->edict)); MSG_WriteByte (&client->message, svc_signonnum); MSG_WriteByte (&client->message, 1); client->sendsignon = true; client->spawned = false; // need prespawn, spawn, etc } /* ================ 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) { edict_t *ent; client_t *client; int edictnum; struct qsocket_s *netconnection; int i; float spawn_parms[NUM_SPAWN_PARMS]; client = svs.clients + clientnum; Con_DPrintf ("Client %s connected\n", NET_QSocketGetAddressString(client->netconnection)); edictnum = clientnum+1; ent = EDICT_NUM(edictnum); // set up the client_t netconnection = client->netconnection; if (sv.loadgame) memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); memset (client, 0, sizeof(*client)); client->netconnection = netconnection; strcpy (client->name, "unconnected"); client->active = true; client->spawned = false; client->edict = ent; client->message.data = client->msgbuf; client->message.maxsize = sizeof(client->msgbuf); client->message.allowoverflow = true; // we can catch it if (sv.loadgame) memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms)); else { // call the progs to get default spawn parms for the new client PR_ExecuteProgram (pr_global_struct->SetNewParms); for (i=0 ; ispawn_parms[i] = (&pr_global_struct->parm1)[i]; } SV_SendServerinfo (client); } /* =================== SV_CheckForNewClients =================== */ void SV_CheckForNewClients (void) { struct qsocket_s *ret; int i; // // check for new connections // while (1) { ret = NET_CheckNewConnections (); if (!ret) break; // // init a new client structure // for (i=0 ; icontents < 0) { if (node->contents != CONTENTS_SOLID) { pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter for (i=0 ; iplane; d = DotProduct (org, plane->normal) - plane->dist; if (d > 8) node = node->children[0]; else if (d < -8) node = node->children[1]; else { // go down both SV_AddToFatPVS (org, node->children[0], worldmodel); //johnfitz -- worldmodel as a parameter node = node->children[1]; } } } /* ============= SV_FatPVS Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the given point. ============= */ byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter { fatbytes = (worldmodel->numleafs+7)>>3; // ericw -- was +31, assumed to be a bug/typo if (fatpvs == NULL || fatbytes > fatpvs_capacity) { fatpvs_capacity = fatbytes; fatpvs = (byte *) realloc (fatpvs, fatpvs_capacity); if (!fatpvs) Sys_Error ("SV_FatPVS: realloc() failed on %d bytes", fatpvs_capacity); } Q_memset (fatpvs, 0, fatbytes); SV_AddToFatPVS (org, worldmodel->nodes, worldmodel); //johnfitz -- worldmodel as a parameter return fatpvs; } /* ============= SV_VisibleToClient -- johnfitz PVS test encapsulated in a nice function ============= */ qboolean SV_VisibleToClient (edict_t *client, edict_t *test, qmodel_t *worldmodel) { byte *pvs; vec3_t org; int i; VectorAdd (client->v.origin, client->v.view_ofs, org); pvs = SV_FatPVS (org, worldmodel); for (i=0 ; i < test->num_leafs ; i++) if (pvs[test->leafnums[i] >> 3] & (1 << (test->leafnums[i]&7) )) return true; return false; } //============================================================================= /* ============= SV_WriteEntitiesToClient ============= */ void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) { int e, i; int bits; byte *pvs; vec3_t org; float miss; edict_t *ent; // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); pvs = SV_FatPVS (org, sv.worldmodel); // send over all entities (excpet the client) that touch the pvs ent = NEXT_EDICT(sv.edicts); for (e=1 ; ev.effects == EF_NODRAW) //sB adding back NODRAW for limbs continue; if (ent != clent) // clent is ALLWAYS sent { // ignore ents without visible models if (!ent->v.modelindex || !PR_GetString(ent->v.model)[0]) continue; //johnfitz -- don't send model>255 entities if protocol is 15 if (sv.protocol == PROTOCOL_NETQUAKE && (int)ent->v.modelindex & 0xFF00) continue; // ignore if not touching a PV leaf for (i=0 ; i < ent->num_leafs ; i++) if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) )) break; // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. // // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs // for us to say whether it's in the PVS, so don't try to vis cull it. // this commonly happens with rotators, because they often have huge bboxes // spanning the entire map, or really tall lifts, etc. if (i == ent->num_leafs && ent->num_leafs < MAX_ENT_LEAFS) continue; // not visible } //johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally //assumed here. And, for protocol 85 the max size is actually 24 bytes. if (msg->cursize + 24 > msg->maxsize) { //johnfitz -- less spammy overflow message if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime ) { Con_Printf ("Packet overflow!\n"); dev_overflows.packetsize = realtime; } goto stats; //johnfitz } // send an update bits = 0; for (i=0 ; i<3 ; i++) { miss = ent->v.origin[i] - ent->baseline.origin[i]; if ( miss < -0.1 || miss > 0.1 ) bits |= U_ORIGIN1<v.angles[0] != ent->baseline.angles[0] ) bits |= U_ANGLE1; if ( ent->v.angles[1] != ent->baseline.angles[1] ) bits |= U_ANGLE2; if ( ent->v.angles[2] != ent->baseline.angles[2] ) bits |= U_ANGLE3; if (ent->v.movetype == MOVETYPE_STEP) bits |= U_STEP; // don't mess up the step animation if (ent->baseline.colormap != ent->v.colormap) bits |= U_COLORMAP; if (ent->baseline.skin != ent->v.skin) bits |= U_SKIN; if (ent->baseline.frame != ent->v.frame) bits |= U_FRAME; if (ent->baseline.effects != ent->v.effects) bits |= U_EFFECTS; if (ent->baseline.modelindex != ent->v.modelindex) bits |= U_MODEL; if (ent->baseline.light_lev != ent->v.light_lev) bits |= U_LIGHTLEVEL; if (ent->v.scale != ENTSCALE_DEFAULT && ent->v.scale != 0) bits |= U_SCALE; //johnfitz -- alpha if (pr_alpha_supported) { // TODO: find a cleaner place to put this code eval_t *val; val = GetEdictFieldValue(ent, "alpha"); if (val) ent->alpha = ENTALPHA_ENCODE(val->_float); } //don't send invisible entities unless they have effects if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) continue; //johnfitz //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { if (ent->baseline.alpha != ent->alpha) bits |= U_ALPHA; if (bits & U_FRAME && (int)ent->v.frame & 0xFF00) bits |= U_FRAME2; if (bits & U_MODEL && (int)ent->v.modelindex & 0xFF00) bits |= U_MODEL2; if (ent->sendinterval) bits |= U_LERPFINISH; if (bits >= 65536) bits |= U_EXTEND1; if (bits >= 16777216) bits |= U_EXTEND2; } //johnfitz if (e >= 256) bits |= U_LONGENTITY; if (bits >= 256) bits |= U_MOREBITS; // // write the message // MSG_WriteByte (msg, bits | U_SIGNAL); if (bits & U_MOREBITS) MSG_WriteByte (msg, bits>>8); //johnfitz -- PROTOCOL_FITZQUAKE if (bits & U_EXTEND1) MSG_WriteByte(msg, bits>>16); if (bits & U_EXTEND2) MSG_WriteByte(msg, bits>>24); //johnfitz if (bits & U_LONGENTITY) MSG_WriteShort (msg,e); else MSG_WriteByte (msg,e); if (bits & U_MODEL) MSG_WriteByte (msg, ent->v.modelindex); if (bits & U_FRAME) MSG_WriteByte (msg, ent->v.frame); if (bits & U_COLORMAP) MSG_WriteByte (msg, ent->v.colormap); if (bits & U_SKIN) MSG_WriteByte (msg, ent->v.skin); if (bits & U_EFFECTS) MSG_WriteShort (msg, ent->v.effects); if (bits & U_ORIGIN1) MSG_WriteCoord (msg, ent->v.origin[0], sv.protocolflags); if (bits & U_ANGLE1) MSG_WriteAngle(msg, ent->v.angles[0], sv.protocolflags); if (bits & U_ORIGIN2) MSG_WriteCoord (msg, ent->v.origin[1], sv.protocolflags); if (bits & U_ANGLE2) MSG_WriteAngle(msg, ent->v.angles[1], sv.protocolflags); if (bits & U_ORIGIN3) MSG_WriteCoord (msg, ent->v.origin[2], sv.protocolflags); if (bits & U_ANGLE3) MSG_WriteAngle(msg, ent->v.angles[2], sv.protocolflags); // NZP START if (bits & U_LIGHTLEVEL) MSG_WriteByte(msg, ent->v.light_lev); // NZP END if (bits & U_SCALE) MSG_WriteByte(msg, ENTSCALE_ENCODE(ent->v.scale)); //johnfitz -- PROTOCOL_FITZQUAKE if (bits & U_ALPHA) MSG_WriteByte(msg, ent->alpha); if (bits & U_FRAME2) MSG_WriteByte(msg, (int)ent->v.frame >> 8); if (bits & U_MODEL2) MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); if (bits & U_LERPFINISH) MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); //johnfitz } //johnfitz -- devstats stats: if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) Con_DWarning ("%i byte packet exceeds standard limit of 1024 (max = %d).\n", msg->cursize, msg->maxsize); dev_stats.packetsize = msg->cursize; dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); //johnfitz } /* ============= SV_CleanupEnts ============= */ void SV_CleanupEnts (void) { int e; edict_t *ent; ent = NEXT_EDICT(sv.edicts); for (e=1 ; ev.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH; } } /* ================== SV_WriteClientdataToMessage ================== */ extern char* pr_strings; void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) { int bits; int i; edict_t *other; int items; eval_t *val; /* sB disabled to allow consistency between platforms // // send a damage message // if (ent->v.dmg_take || ent->v.dmg_save) { other = PROG_TO_EDICT(ent->v.dmg_inflictor); MSG_WriteByte (msg, svc_damage); MSG_WriteByte (msg, ent->v.dmg_save); MSG_WriteByte (msg, ent->v.dmg_take); for (i=0 ; i<3 ; i++) MSG_WriteCoord (msg, other->v.origin[i] + 0.5*(other->v.mins[i] + other->v.maxs[i]), sv.protocolflags ); ent->v.dmg_take = 0; ent->v.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 ( ent->v.fixangle ) { MSG_WriteByte (msg, svc_setangle); for (i=0 ; i < 3 ; i++) MSG_WriteAngle (msg, ent->v.angles[i], sv.protocolflags ); ent->v.fixangle = 0; } bits = 0; if (ent->v.view_ofs[2] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; if (ent->v.idealpitch) bits |= SU_IDEALPITCH; if (ent->v.perks) bits |= SU_PERKS; if (ent->v.maxspeed) bits |= SU_MAXSPEED; if (ent->v.facingenemy) bits |= SU_FACINGENEMY; if (ent->v.Weapon_Name_Touch) bits |= SU_TOUCHNAME; if ( (int)ent->v.flags & FL_ONGROUND) bits |= SU_ONGROUND; // cypress - Water stuff is useless. // if ( ent->v.waterlevel >= 2) // bits |= SU_INWATER; for (i=0 ; i<3 ; i++) { if (ent->v.punchangle[i]) bits |= (SU_PUNCH1<v.velocity[i]) bits |= (SU_VELOCITY1<v.weaponframe) bits |= SU_WEAPONFRAME; if (ent->v.weaponskin) bits |= SU_WEAPONSKIN; if ((int)ent->v.weapon != ent->last_weapon) { bits |= SU_WEAPON; ent->last_weapon = (int)ent->v.weapon; // cypress -- don't network a bunch of weapon shit. also, why is this a float? } if (ent->v.grenades) bits |= SU_GRENADES; //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { if (bits & SU_WEAPON && SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) & 0xFF00) bits |= SU_WEAPON2; if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2; if (bits & SU_WEAPONFRAME && (int)ent->v.weaponframe & 0xFF00) bits |= SU_WEAPONFRAME2; if (bits & SU_WEAPON && ent->alpha != ENTALPHA_DEFAULT) bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha if (bits >= 65536) bits |= SU_EXTEND1; if (bits >= 16777216) bits |= SU_EXTEND2; } //johnfitz // send the data MSG_WriteByte (msg, svc_clientdata); MSG_WriteShort (msg, bits); //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16); if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24); //johnfitz if (bits & SU_VIEWHEIGHT) MSG_WriteChar (msg, ent->v.view_ofs[2]); if (bits & SU_IDEALPITCH) MSG_WriteChar (msg, ent->v.idealpitch); for (i=0 ; i<3 ; i++) { if (bits & (SU_PUNCH1<v.punchangle[i]); if (bits & (SU_VELOCITY1<v.velocity[i]/16); } // FIXME: We allow QC to change Flash_Offset on-the-fly // for left-handed weapons. May want to make this it's // own field, but until then it's float spam! for(i = 0; i < 3; i++) MSG_WriteFloat(msg, ent->v.Flash_Offset[i]); // WHY WAS THIS A LONG???? if (bits & SU_PERKS) MSG_WriteByte (msg, ent->v.perks); if (bits & SU_MAXSPEED) MSG_WriteFloat (msg, ent->v.maxspeed); if (bits & SU_FACINGENEMY) MSG_WriteByte (msg, ent->v.facingenemy); // FIXME: I don't really like forcing a pass of 32 bytes per frame whenever // we touch a weapon or something. Kinda lame. if (bits & SU_TOUCHNAME) { size_t len = 32; if (strlen(pr_strings+ent->v.Weapon_Name_Touch) < 32) len = strlen(pr_strings+ent->v.Weapon_Name_Touch); MSG_WriteByte(msg, len); for(i = 0; i < len; i++) { MSG_WriteChar(msg, (pr_strings+ent->v.Weapon_Name_Touch)[i]); } } if (bits & SU_WEAPONFRAME) MSG_WriteByte (msg, ent->v.weaponframe); if (bits & SU_WEAPONSKIN) MSG_WriteByte (msg, ent->v.weaponskin); // Active Weapon MSG_WriteByte (msg, ent->v.weapon); // Weapon model index MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel))); // Cypress -- SU_WEAPON exchanges are now a lot more involved, so they only // happen if absolutely necessary. if (bits & SU_WEAPON) { // Weapon Name size_t len = 32; // hard-coded 32 character limit, deal with it. if (strlen(pr_strings+ent->v.Weapon_Name) < 32) len = strlen(pr_strings+ent->v.Weapon_Name); MSG_WriteByte(msg, len); for(i = 0; i < len; i++) { MSG_WriteChar(msg, (pr_strings+ent->v.Weapon_Name)[i]); } // Weapon ADS Offset for(i = 0; i < 3; i++) MSG_WriteFloat(msg, ent->v.ADS_Offset[i]); // Muzzle flash size MSG_WriteByte(msg, ent->v.Flash_Size); } // Why the fuck was this a long? if (bits & SU_GRENADES) MSG_WriteByte(msg, ent->v.grenades); // Why the FUCK were these shorts?!?!? MSG_WriteByte (msg, ent->v.primary_grenades); MSG_WriteByte (msg, ent->v.secondary_grenades); MSG_WriteByte (msg, ent->v.health); // this one was id's 'fault' MSG_WriteByte (msg, ent->v.currentammo); MSG_WriteByte (msg, ent->v.currentmag); MSG_WriteByte (msg, ent->v.zoom); MSG_WriteByte (msg, pr_global_struct->rounds); // This cooresponds to CL_ParseClientdata MSG_WriteByte (msg, pr_global_struct->rounds_change); MSG_WriteByte (msg, ent->v.x2_icon); MSG_WriteByte (msg, ent->v.insta_icon); MSG_WriteByte (msg, ent->v.progress_bar); MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weapon2model))); MSG_WriteByte (msg, ent->v.weapon2skin); MSG_WriteByte (msg, ent->v.weapon2frame); MSG_WriteByte (msg, ent->v.currentmag2); //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_WEAPON2) MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) >> 8); if (bits & SU_AMMO2) MSG_WriteByte (msg, (int)ent->v.currentammo >> 8); if (bits & SU_WEAPONFRAME2) MSG_WriteByte (msg, (int)ent->v.weaponframe >> 8); if (bits & SU_WEAPONALPHA) MSG_WriteByte (msg, ent->alpha); //for now, weaponalpha = client entity alpha //johnfitz } /* ======================= SV_SendClientDatagram ======================= */ qboolean SV_SendClientDatagram (client_t *client) { byte buf[MAX_DATAGRAM]; sizebuf_t msg; msg.data = buf; msg.maxsize = sizeof(buf); msg.cursize = 0; //johnfitz -- if client is nonlocal, use smaller max size so packets aren't fragmented if (strcmp(NET_QSocketGetAddressString(client->netconnection), "LOCAL") != 0) msg.maxsize = DATAGRAM_MTU; //johnfitz MSG_WriteByte (&msg, svc_time); MSG_WriteFloat (&msg, sv.time); // add the client specific data to the datagram SV_WriteClientdataToMessage (client->edict, &msg); SV_WriteEntitiesToClient (client->edict, &msg); // copy the server datagram if there is space if (msg.cursize + sv.datagram.cursize < msg.maxsize) SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); // send the datagram if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) { SV_DropClient (true);// if the message couldn't send, kick off return false; } return true; } /* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages (void) { int i, j; client_t *client; // check for changes to be sent over the reliable streams for (i=0, host_client = svs.clients ; iold_points != host_client->edict->v.points) { for (j=0, client = svs.clients ; jactive) continue; MSG_WriteByte (&client->message, svc_updatepoints); MSG_WriteByte (&client->message, i); MSG_WriteLong (&client->message, host_client->edict->v.points); } host_client->old_points = host_client->edict->v.points; } } for (i=0, host_client = svs.clients ; iold_kills != host_client->edict->v.kills) { for (j=0, client = svs.clients ; jactive) continue; MSG_WriteByte (&client->message, svc_updatekills); MSG_WriteByte (&client->message, i); MSG_WriteShort (&client->message, host_client->edict->v.kills); } host_client->old_points = host_client->edict->v.points; } } for (j=0, client = svs.clients ; jactive) continue; SZ_Write (&client->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); } SZ_Clear (&sv.reliable_datagram); } /* ======================= SV_SendNop Send a nop message without trashing or sending the accumulated client message buffer ======================= */ void SV_SendNop (client_t *client) { sizebuf_t msg; byte buf[4]; msg.data = buf; msg.maxsize = sizeof(buf); msg.cursize = 0; MSG_WriteChar (&msg, svc_nop); if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) SV_DropClient (true); // if the message couldn't send, kick off client->last_message = realtime; } /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages (void) { int i; // update frags, names, etc SV_UpdateToReliableMessages (); // build individual updates for (i=0, host_client = svs.clients ; iactive) continue; if (host_client->spawned) { if (!SV_SendClientDatagram (host_client)) continue; } else { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed // send a full message when the next signon stage has been requested // some other message data (name changes, etc) may accumulate // between signon stages if (!host_client->sendsignon) { if (realtime - host_client->last_message > 5) SV_SendNop (host_client); continue; // don't send out non-signon messages } } // check for an overflowed message. Should only happen // on a very fucked up connection that backs up a lot, then // changes level if (host_client->message.overflowed) { SV_DropClient (true); host_client->message.overflowed = false; continue; } if (host_client->message.cursize || host_client->dropasap) { if (!NET_CanSendMessage (host_client->netconnection)) { // I_Printf ("can't write\n"); continue; } if (host_client->dropasap) SV_DropClient (false); // went to another level else { if (NET_SendMessage (host_client->netconnection , &host_client->message) == -1) SV_DropClient (true); // if the message couldn't send, kick off SZ_Clear (&host_client->message); host_client->last_message = realtime; host_client->sendsignon = false; } } } // clear muzzle flashes SV_CleanupEnts (); } /* ============================================================================== SERVER SPAWNING ============================================================================== */ /* ================ SV_ModelIndex ================ */ int SV_ModelIndex (const char *name) { int i; if (!name || !name[0]) return 0; for (i=0 ; ifree) continue; if (entnum > svs.maxclients && !svent->v.modelindex) continue; // // create entity baseline // VectorCopy (svent->v.origin, svent->baseline.origin); VectorCopy (svent->v.angles, svent->baseline.angles); svent->baseline.frame = svent->v.frame; svent->baseline.skin = svent->v.skin; if (entnum > 0 && entnum <= svs.maxclients) { svent->baseline.colormap = entnum; svent->baseline.modelindex = SV_ModelIndex("models/player.mdl"); svent->baseline.alpha = ENTALPHA_DEFAULT; //johnfitz -- alpha support svent->baseline.light_lev = 0; // cypress -- light level support } else { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support svent->baseline.light_lev = 0; // cypress -- light level support } //johnfitz -- PROTOCOL_FITZQUAKE bits = 0; if (sv.protocol == PROTOCOL_NETQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values { if (svent->baseline.modelindex & 0xFF00) svent->baseline.modelindex = 0; if (svent->baseline.frame & 0xFF00) svent->baseline.frame = 0; svent->baseline.alpha = ENTALPHA_DEFAULT; svent->baseline.light_lev = 0; } else //decide which extra data needs to be sent { if (svent->baseline.modelindex & 0xFF00) bits |= B_LARGEMODEL; if (svent->baseline.frame & 0xFF00) bits |= B_LARGEFRAME; if (svent->baseline.alpha != ENTALPHA_DEFAULT) bits |= B_ALPHA; if (svent->baseline.light_lev != 0) bits |= B_LIGHTLEVEL; } //johnfitz // // add to the message // //johnfitz -- PROTOCOL_FITZQUAKE if (bits) MSG_WriteByte (&sv.signon, svc_spawnbaseline2); else MSG_WriteByte (&sv.signon, svc_spawnbaseline); //johnfitz MSG_WriteShort (&sv.signon,entnum); //johnfitz -- PROTOCOL_FITZQUAKE if (bits) MSG_WriteByte (&sv.signon, bits); if (bits & B_LARGEMODEL) MSG_WriteShort (&sv.signon, svent->baseline.modelindex); else MSG_WriteByte (&sv.signon, svent->baseline.modelindex); if (bits & B_LARGEFRAME) MSG_WriteShort (&sv.signon, svent->baseline.frame); else MSG_WriteByte (&sv.signon, svent->baseline.frame); //johnfitz MSG_WriteByte (&sv.signon, svent->baseline.colormap); MSG_WriteByte (&sv.signon, svent->baseline.skin); for (i=0 ; i<3 ; i++) { MSG_WriteCoord(&sv.signon, svent->baseline.origin[i], sv.protocolflags); MSG_WriteAngle(&sv.signon, svent->baseline.angles[i], sv.protocolflags); } //johnfitz -- PROTOCOL_FITZQUAKE if (bits & B_ALPHA) MSG_WriteByte (&sv.signon, svent->baseline.alpha); //johnfitz // NZP START if (bits & B_LIGHTLEVEL) MSG_WriteByte (&sv.signon, svent->baseline.light_lev); // NZP END } } /* ================ SV_SendReconnect Tell all the clients that the server is changing levels ================ */ void SV_SendReconnect (void) { byte data[128]; sizebuf_t msg; msg.data = data; msg.cursize = 0; msg.maxsize = sizeof(data); MSG_WriteChar (&msg, svc_stufftext); MSG_WriteString (&msg, "reconnect\n"); NET_SendToAll (&msg, 5.0); if (!isDedicated) Cmd_ExecuteString ("reconnect\n", src_command); } /* ================ SV_SaveSpawnparms Grabs the current state of each client for saving across the transition to another level ================ */ void SV_SaveSpawnparms (void) { int i, j; svs.serverflags = pr_global_struct->serverflags; for (i=0, host_client = svs.clients ; iactive) continue; // call the progs to get default spawn parms for the new client pr_global_struct->self = EDICT_TO_PROG(host_client->edict); PR_ExecuteProgram (pr_global_struct->SetChangeParms); for (j=0 ; jspawn_parms[j] = (&pr_global_struct->parm1)[j]; } } /* ================ SV_SpawnServer This is called at the start of each level ================ */ extern float scr_centertime_off; void Load_Waypoint (); void SV_SpawnServer (const char *server) { static char dummy[8] = { 0,0,0,0,0,0,0,0 }; edict_t *ent; int i; // let's not have any servers with no name if (hostname.string[0] == 0) Cvar_Set ("hostname", "UNNAMED"); scr_centertime_off = 0; Con_DPrintf ("SpawnServer: %s\n",server); svs.changelevel_issued = false; // now safe to issue another // // tell all connected clients that we are going to a new level // if (sv.active) { SV_SendReconnect (); } // // make cvars consistant // if (coop.value) Cvar_Set ("deathmatch", "0"); current_skill = (int)(skill.value + 0.5); if (current_skill < 0) current_skill = 0; if (current_skill > 3) current_skill = 3; Cvar_SetValue ("skill", (float)current_skill); // // set up the new server // //memset (&sv, 0, sizeof(sv)); Host_ClearMemory (); q_strlcpy (sv.name, server, sizeof(sv.name)); sv.protocol = sv_protocol; // johnfitz if (sv.protocol == PROTOCOL_RMQ) { // set up the protocol flags used by this server // (note - these could be cvar-ised so that server admins could choose the protocol features used by their servers) sv.protocolflags = PRFL_INT32COORD | PRFL_SHORTANGLE; } else sv.protocolflags = 0; // load progs to get entity field count PR_LoadProgs (); // allocate server memory /* Host_ClearMemory() called above already cleared the whole sv structure */ sv.max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar sv.edicts = (edict_t *) malloc (sv.max_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() 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 sv.num_edicts = svs.maxclients+1; memset(sv.edicts, 0, sv.num_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() for (i=0 ; inumsubmodels ; i++) { sv.model_precache[1+i] = localmodels[i]; sv.models[i+1] = Mod_ForName (localmodels[i], false); } // // load the rest of the entities // ent = EDICT_NUM(0); memset (&ent->v, 0, progs->entityfields * 4); ent->free = false; ent->v.model = PR_SetEngineString(sv.worldmodel->name); ent->v.modelindex = 1; // world model ent->v.solid = SOLID_BSP; ent->v.movetype = MOVETYPE_PUSH; if (coop.value) pr_global_struct->coop = coop.value; else pr_global_struct->deathmatch = deathmatch.value; pr_global_struct->mapname = PR_SetEngineString(sv.name); // serverflags are for cross level information (sigils) pr_global_struct->serverflags = svs.serverflags; ED_LoadFromFile (sv.worldmodel->entities); sv.active = true; // all setup is completed, any further precache statements are errors sv.state = ss_active; // run two frames to allow everything to settle host_frametime = 0.1; SV_Physics (); SV_Physics (); // create a baseline for more efficient communications SV_CreateBaseline (); //johnfitz -- warn if signon buffer larger than standard server can handle if (sv.signon.cursize > 8000-2) //max size that will fit into 8000-sized client->message buffer with 2 extra bytes on the end Con_DWarning ("%i byte signon buffer exceeds standard limit of 7998 (max = %d).\n", sv.signon.cursize, sv.signon.maxsize); //johnfitz // send serverinfo to all connected clients for (i=0,host_client = svs.clients ; iactive) SV_SendServerinfo (host_client); Load_Waypoint (); Con_DPrintf ("Server spawned.\n"); } //ZOMBIE AI THINGS BELOVE THIS!!! #define W_MAX_TEMPSTRING 2048 char *w_string_temp; int W_fopen (void) { int h = 0; Sys_FileOpenRead (va("%s/maps/%s.way",com_gamedir, sv.name), &h); return h; } int W_fopenbeta(void) { int h = 0; Sys_FileOpenRead (va("%s/data/%s",com_gamedir, sv.name), &h); return h; } void W_fclose (int h) { Sys_FileClose(h); } char *W_fgets (int h) { // reads one line (up to a \n) into a string int i; int count; char buffer; count = Sys_FileRead(h, &buffer, 1); if (count && buffer == '\r') // carriage return { count = Sys_FileRead(h, &buffer, 1); // skip } if (!count) // EndOfFile { return ""; } i = 0; while (count && buffer != '\n') { if (i < 128-1) // no place for character in temp string { w_string_temp[i++] = buffer; } // read next character count = Sys_FileRead(h, &buffer, 1); if (count && buffer == '\r') // carriage return { count = Sys_FileRead(h, &buffer, 1); // skip } }; w_string_temp[i] = 0; return (w_string_temp); } char *W_substring (char *p, int offset, int length) { int maxoffset; // 2001-10-25 Enhanced temp string handling by Maddes // cap values maxoffset = strlen(p); if (offset > maxoffset) { offset = maxoffset; } if (offset < 0) offset = 0; // 2001-10-25 Enhanced temp string handling by Maddes start if (length >= maxoffset) length = maxoffset-1; // 2001-10-25 Enhanced temp string handling by Maddes end if (length < 0) length = 0; p += offset; strncpy(w_string_temp, p, length); w_string_temp[length]=0; return w_string_temp; } void W_stov (char *v, vec3_t out) { int i; vec3_t d; for (i=0; i<3; i++) { while(v && (v[0] == ' ' || v[0] == '\'')) //skip unneeded data v++; d[i] = atof(v); while (v && v[0] != ' ') // skip to next space v++; } VectorCopy (d, out); } waypoint_ai waypoints[MAX_WAYPOINTS]; int n_waypoints; // // Load_Waypoint_NZPBETA // Attempts to load an NZ:P Beta formatted // Waypoint file. // void Load_Waypoint_NZPBETA() { char temp[64]; int i, p, s; int h = 0; // Keep track of the waypoint with the highest index we've loaded int max_waypoint_idx = -1; int n_waypoints_parsed = 0; h = W_fopenbeta(); if (h == -1) { return; // don't bother notifying.. } for (i = 0; i < MAX_WAYPOINTS; i++) { waypoints[i].used = 0; // waypoints[i].id = -1; for (p = 0; p < 8; p++) { waypoints[i].target[p] = -1; // waypoints[i].target_id[p] = -1; } } for (i = 0; i < MAX_EDICTS; i++) { closest_waypoints[i] = -1; } i = 0; Con_DPrintf("Loading BETA waypoints\n"); vec3_t way_origin; int way_id; while (1) { // End of file. if (!strcmp(W_fgets(h), "")) { break; } W_stov(w_string_temp, way_origin); // int waypoint_idx = atoi(W_fgets(h)) - 1; // (1-based index, swap to 0-based) n_waypoints_parsed += 1; if(i > max_waypoint_idx) { max_waypoint_idx = i; } if(waypoint_idx >= MAX_WAYPOINTS) { Sys_Error ("Waypoint with idx %d past MAX_WAYPOINTS {%i)\n", waypoint_idx, MAX_WAYPOINTS); } // waypoints[waypoint_idx].id = way_id; VectorCopy(way_origin, waypoints[waypoint_idx].origin); // [link1, link2, link3, link4, owner1, owner2, owner3, owner4] for(i = 0; i < 8; i++) { W_fgets(h); // Skip "link1..link4" // Parse "owner1..owner4" if (i >= 4) { int src_waypoint_idx = atoi(w_string_temp) - 1; // Fix 0-based index if (src_waypoint_idx >= 0) { // Search for an empty slot in waypoint `src_waypoint_idx` for(int j = 0; j < 8; j++) { if(waypoints[src_waypoint_idx].target[j] < 0) { waypoints[src_waypoint_idx].target[j] = waypoint_idx; } } } } } waypoints[waypoint_idx].used = 1; waypoints[waypoint_idx].open = 1; } Con_DPrintf("Total waypoints: %i, num parsed: %i\n", max_waypoint_idx, n_waypoints_parsed); // Store in global `n_waypoints` n_waypoints = max_waypoint_idx; // Cache distance between waypoints for(i = 0; i < MAX_WAYPOINTS; i++) { for(p = 0; p < 8; p++) { if(waypoints[i].target[p] < 0) { continue; } float dist = VecLength2(waypoints[s].origin, waypoints[i].origin); waypoints[i].dist[p] = dist; } Con_DPrintf("Waypoint (%i)\n target1: (%i, %f),\n target2: (%i, %f),\n target3: (%i, %f),\n target4: (%i, %f),\n target5: (%i, %f),\n target6: (%i, %f),\n target7: (%i, %f),\n target8: (%i, %f)\n", i, waypoints[i].target[0], waypoints[i].dist[0], waypoints[i].target[1], waypoints[i].dist[1], waypoints[i].target[2], waypoints[i].dist[2], waypoints[i].target[3], waypoints[i].dist[3], waypoints[i].target[4], waypoints[i].dist[4], waypoints[i].target[5], waypoints[i].dist[5], waypoints[i].target[6], waypoints[i].dist[6], waypoints[i].target[7], waypoints[i].dist[7] ); } W_fclose(h); } // // Some waypoint slots in the global list may not have been loaded // Look for these empty slots, and shift all waypoints down to fill them // This also fixes waypoint reference links // void cleanup_waypoints() { int new_n_waypoints = 0; for(int i = 0; i < MAX_WAYPOINTS; i++) { // If waypoint slot is used, count it if(waypoints[i].used) { new_n_waypoints += 1; } // If waypoint slot is unused... else { // Update all waypoint link references greater than this waypoint slot index down one for(int j = 0; j < MAX_WAYPOINTS; j++) { if(waypoints[j].used) { for(int k = 0; k < 8; k++) { if(waypoints[j].target[k] > i) { waypoints[j].target[k] -= 1; } } } } // Move all waypoints after this down one slot: for(int j = i; j < MAX_WAYPOINTS - 1; j++) { memcpy(&(waypoints[j]), &(waypoints[j+1]), sizeof(waypoint_ai)); } // Mark waypoint slot at the end of the list as unused waypoints[MAX_WAYPOINTS-1].used = 0; // Count how many used waypoint slots are to the right of index `i` int n_remaining_waypoints = 0; for(int j = i; j < MAX_WAYPOINTS - 1; j++) { if(waypoints[j].used) { n_remaining_waypoints += 1; } } // If no remaining used waypoint slots, stop if(n_remaining_waypoints == 0) { break; } // Search this index again i -= 1; } } n_waypoints = new_n_waypoints; } void Load_Waypoint () { char temp[64]; int p, s; vec3_t d; int h = 0; // --------------------------------------- // Clear the structs // --------------------------------------- n_waypoints = 0; for (int i = 0; i < MAX_WAYPOINTS; i++) { waypoints[i].used = 0; // waypoints[i].id = -1; for (p = 0; p < 8; p++) { waypoints[i].target[p] = -1; // waypoints[i].target_id[p] = -1; } } for (int i = 0; i < MAX_EDICTS; i++) { closest_waypoints[i] = -1; } // --------------------------------------- h = W_fopen(); w_string_temp = Z_Malloc(128); if (h == -1) { Con_DPrintf("No waypoint file (%s/maps/%s.way) found, trying beta format..\n", com_gamedir, sv.name); Load_Waypoint_NZPBETA(); cleanup_waypoints(); return; } int i; // Keep track of the waypoint with the highest index we've loaded int max_waypoint_idx = -1; int n_waypoints_parsed = 0; Con_DPrintf("Loading waypoints\n"); while (1) { if (strncmp(W_fgets (h), "Waypoint", 8)) { Con_DPrintf("Last waypoint\n"); break; } else { W_fgets (h); W_stov (W_substring (W_fgets (h), 9, 20), d); strcpy(temp, W_substring (W_fgets (h), 5, 20)); i = atoi (temp); if (i >= MAX_WAYPOINTS) { Sys_Error ("Waypoint with id %d past MAX_WAYPOINTS {%i)\n", i, MAX_WAYPOINTS); } n_waypoints_parsed += 1; if(i > max_waypoint_idx) { max_waypoint_idx = i; } VectorCopy (d, waypoints[i].origin); strcpy(waypoints[i].special, W_substring (W_fgets (h), 10, 20)); if (waypoints[i].special[0]) { waypoints[i].open = 0; } else { waypoints[i].open = 1; } // Note: this block makes sure that empty/invalid neighbors are always packed to the end // In other words, when iterating from start, first empty means rest are empty too. int slot = 0; for (int t = 0; t < 8; t++) { int start = t == 0 ? 9 : 10; strcpy(temp, W_substring (W_fgets (h), start, 20)); if (isdigit(temp[0])) { waypoints[i].target[slot] = atoi (temp); slot++; } } W_fgets (h); W_fgets (h); waypoints[i].used = 1; Con_DPrintf("Waypoint (%i), tag: %s, open: %i, target1: %i, target2: %i, target3: %i, target4: %i, target5: %i, target6: %i, target7: %i, target8: %i\n", i, waypoints[i].special, waypoints[i].open, waypoints[i].target[0], waypoints[i].target[1], waypoints[i].target[2], waypoints[i].target[3], waypoints[i].target[4], waypoints[i].target[5], waypoints[i].target[6], waypoints[i].target[7] ); } } Con_DPrintf("Total waypoints: %i, num parsed: %i\n", max_waypoint_idx, n_waypoints_parsed); // Store in global `n_waypoints` n_waypoints = max_waypoint_idx; // Cache distance between waypoints for(i = 0; i < MAX_WAYPOINTS; i++) { for(p = 0; p < 8; p++) { if(waypoints[i].target[p] < 0) { continue; } float dist = VecLength2(waypoints[s].origin, waypoints[i].origin); waypoints[i].dist[p] = dist; } Con_DPrintf("Waypoint (%i)\n target1: (%i, %f),\n target2: (%i, %f),\n target3: (%i, %f),\n target4: (%i, %f),\n target5: (%i, %f),\n target6: (%i, %f),\n target7: (%i, %f),\n target8: (%i, %f)\n", i, waypoints[i].target[0], waypoints[i].dist[0], waypoints[i].target[1], waypoints[i].dist[1], waypoints[i].target[2], waypoints[i].dist[2], waypoints[i].target[3], waypoints[i].dist[3], waypoints[i].target[4], waypoints[i].dist[4], waypoints[i].target[5], waypoints[i].dist[5], waypoints[i].target[6], waypoints[i].dist[6], waypoints[i].target[7], waypoints[i].dist[7] ); } W_fclose(h); //Z_Free (w_string_temp); cleanup_waypoints(); } // Util for qsort int argsort_comparator(const void *lhs, const void *rhs) { return ((argsort_entry_t*)lhs)->value - ((argsort_entry_t*)rhs)->value; }