/// <summary> /// 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. /// </summary> static bool RunThink(edict_t ent) { float thinktime; thinktime = ent.v.nextthink; if (thinktime <= 0 || thinktime > sv.time + Host.FrameTime) { return(true); } if (thinktime < sv.time) { thinktime = (float)sv.time; // don't let things stay in the past. } // it is possible to start that way // by a trigger with a local time. ent.v.nextthink = 0; Progs.GlobalStruct.time = thinktime; Progs.GlobalStruct.self = EdictToProg(ent); Progs.GlobalStruct.other = EdictToProg(sv.edicts[0]); Progs.Execute(ent.v.think); return(!ent.free); }
/// <summary> /// SV_DropClient /// Called when the player is getting totally kicked off the host /// if (crash = true), don't bother sending signofs /// </summary> public static void DropClient(bool crash) { client_t client = Host.HostClient; if (!crash) { // send any final messages (don't check for errors) if (Net.CanSendMessage(client.netconnection)) { MsgWriter msg = client.message; msg.WriteByte(Protocol.svc_disconnect); Net.SendMessage(client.netconnection, msg); } if (client.edict != null && client.spawned) { // call the prog function for removing a client // this will set the body to a dead frame, among other things int saveSelf = Progs.GlobalStruct.self; Progs.GlobalStruct.self = EdictToProg(client.edict); Progs.Execute(Progs.GlobalStruct.ClientDisconnect); Progs.GlobalStruct.self = saveSelf; } Con.DPrint("Client {0} removed\n", client.name); } // break the net connection Net.Close(client.netconnection); client.netconnection = null; // free the client (the body stays around) client.active = false; client.name = null; client.old_frags = -999999; Net.ActiveConnections--; // send notification to all clients for (int i = 0; i < Server.svs.maxclients; i++) { client_t cl = Server.svs.clients[i]; if (!cl.active) { continue; } cl.message.WriteByte(Protocol.svc_updatename); cl.message.WriteByte(Host.ClientNum); cl.message.WriteString(""); cl.message.WriteByte(Protocol.svc_updatefrags); cl.message.WriteByte(Host.ClientNum); cl.message.WriteShort(0); cl.message.WriteByte(Protocol.svc_updatecolors); cl.message.WriteByte(Host.ClientNum); cl.message.WriteByte(0); } }
/// <summary> /// SV_TouchLinks /// </summary> static void TouchLinks(edict_t ent, areanode_t node) { // touch linked edicts LinkList next; for (LinkList l = node.trigger_edicts.Next; l != node.trigger_edicts; l = next) { next = l.Next; edict_t touch = (edict_t)l.Owner;// EDICT_FROM_AREA(l); if (touch == ent) { continue; } if (touch.v.touch == 0 || touch.v.solid != Solids.SOLID_TRIGGER) { continue; } if (ent.v.absmin.x > touch.v.absmax.x || ent.v.absmin.y > touch.v.absmax.y || ent.v.absmin.z > touch.v.absmax.z || ent.v.absmax.x < touch.v.absmin.x || ent.v.absmax.y < touch.v.absmin.y || ent.v.absmax.z < touch.v.absmin.z) { continue; } int old_self = Progs.GlobalStruct.self; int old_other = Progs.GlobalStruct.other; Progs.GlobalStruct.self = EdictToProg(touch); Progs.GlobalStruct.other = EdictToProg(ent); Progs.GlobalStruct.time = (float)sv.time; Progs.Execute(touch.v.touch); Progs.GlobalStruct.self = old_self; Progs.GlobalStruct.other = old_other; } // recurse down both sides if (node.axis == -1) { return; } if (Mathlib.Comp(ref ent.v.absmax, node.axis) > node.dist) { TouchLinks(ent, node.children[0]); } if (Mathlib.Comp(ref ent.v.absmin, node.axis) < node.dist) { TouchLinks(ent, node.children[1]); } }
/// <summary> /// SV_SaveSpawnparms /// Grabs the current state of each client for saving across the /// transition to another level /// </summary> public static void SaveSpawnparms() { Server.svs.serverflags = (int)Progs.GlobalStruct.serverflags; for (int i = 0; i < svs.maxclients; i++) { Host.HostClient = Server.svs.clients[i]; if (!Host.HostClient.active) { continue; } // call the progs to get default spawn parms for the new client Progs.GlobalStruct.self = EdictToProg(Host.HostClient.edict); Progs.Execute(Progs.GlobalStruct.SetChangeParms); AssignGlobalSpawnparams(Host.HostClient); } }
/// <summary> /// Host_Kill_f /// </summary> private static void Kill_f() { if (Cmd.Source == cmd_source_t.src_command) { Cmd.ForwardToServer(); return; } if (Server.Player.v.health <= 0) { Server.ClientPrint("Can't suicide -- allready dead!\n"); return; } Progs.GlobalStruct.time = (float)Server.sv.time; Progs.GlobalStruct.self = Server.EdictToProg(Server.Player); Progs.Execute(Progs.GlobalStruct.ClientKill); }
/// <summary> /// 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. /// </summary> static void ConnectClient(int clientnum) { client_t client = svs.clients[clientnum]; Con.DPrint("Client {0} connected\n", client.netconnection.address); int edictnum = clientnum + 1; edict_t ent = EdictNum(edictnum); // set up the client_t qsocket_t netconnection = client.netconnection; float[] spawn_parms = new float[NUM_SPAWN_PARMS]; if (sv.loadgame) { Array.Copy(client.spawn_parms, spawn_parms, spawn_parms.Length); } client.Clear(); client.netconnection = netconnection; client.name = "unconnected"; client.active = true; client.spawned = false; client.edict = ent; client.message.AllowOverflow = true; // we can catch it client.privileged = false; if (sv.loadgame) { Array.Copy(spawn_parms, client.spawn_parms, spawn_parms.Length); } else { // call the progs to get default spawn parms for the new client Progs.Execute(Progs.GlobalStruct.SetNewParms); AssignGlobalSpawnparams(client); } SendServerInfo(client); }
/// <summary> /// SV_Impact /// Two entities have touched, so run their touch functions /// </summary> static void Impact(edict_t e1, edict_t e2) { int old_self = Progs.GlobalStruct.self; int old_other = Progs.GlobalStruct.other; Progs.GlobalStruct.time = (float)sv.time; if (e1.v.touch != 0 && e1.v.solid != Solids.SOLID_NOT) { Progs.GlobalStruct.self = EdictToProg(e1); Progs.GlobalStruct.other = EdictToProg(e2); Progs.Execute(e1.v.touch); } if (e2.v.touch != 0 && e2.v.solid != Solids.SOLID_NOT) { Progs.GlobalStruct.self = EdictToProg(e2); Progs.GlobalStruct.other = EdictToProg(e1); Progs.Execute(e2.v.touch); } Progs.GlobalStruct.self = old_self; Progs.GlobalStruct.other = old_other; }
/// <summary> /// SV_Physics_Pusher /// </summary> static void Physics_Pusher(edict_t ent) { float oldltime = ent.v.ltime; float thinktime = ent.v.nextthink; float movetime; if (thinktime < ent.v.ltime + Host.FrameTime) { movetime = thinktime - ent.v.ltime; if (movetime < 0) { movetime = 0; } } else { movetime = (float)Host.FrameTime; } if (movetime != 0) { PushMove(ent, movetime); // advances ent.v.ltime if not blocked } if (thinktime > oldltime && thinktime <= ent.v.ltime) { ent.v.nextthink = 0; Progs.GlobalStruct.time = (float)sv.time; Progs.GlobalStruct.self = EdictToProg(ent); Progs.GlobalStruct.other = EdictToProg(sv.edicts[0]); Progs.Execute(ent.v.think); if (ent.free) { return; } } }
/// <summary> /// SV_Physics_Client /// Player character actions /// </summary> static void Physics_Client(edict_t ent, int num) { if (!svs.clients[num - 1].active) { return; // unconnected slot } // // call standard client pre-think // Progs.GlobalStruct.time = (float)sv.time; Progs.GlobalStruct.self = EdictToProg(ent); Progs.Execute(Progs.GlobalStruct.PlayerPreThink); // // do a move // CheckVelocity(ent); // // decide which move function to call // switch ((int)ent.v.movetype) { case Movetypes.MOVETYPE_NONE: if (!RunThink(ent)) { return; } break; case Movetypes.MOVETYPE_WALK: if (!RunThink(ent)) { return; } if (!CheckWater(ent) && ((int)ent.v.flags & EdictFlags.FL_WATERJUMP) == 0) { AddGravity(ent); } CheckStuck(ent); WalkMove(ent); break; case Movetypes.MOVETYPE_TOSS: case Movetypes.MOVETYPE_BOUNCE: Physics_Toss(ent); break; case Movetypes.MOVETYPE_FLY: if (!RunThink(ent)) { return; } FlyMove(ent, (float)Host.FrameTime, null); break; case Movetypes.MOVETYPE_NOCLIP: if (!RunThink(ent)) { return; } Mathlib.VectorMA(ref ent.v.origin, (float)Host.FrameTime, ref ent.v.velocity, out ent.v.origin); break; default: Sys.Error("SV_Physics_client: bad movetype {0}", (int)ent.v.movetype); break; } // // call standard player post-think // LinkEdict(ent, true); Progs.GlobalStruct.time = (float)sv.time; Progs.GlobalStruct.self = EdictToProg(ent); Progs.Execute(Progs.GlobalStruct.PlayerPostThink); }
/// <summary> /// SV_Physics /// </summary> public static void Physics() { // let the progs know that a new frame has started Progs.GlobalStruct.self = EdictToProg(sv.edicts[0]); Progs.GlobalStruct.other = Progs.GlobalStruct.self; Progs.GlobalStruct.time = (float)sv.time; Progs.Execute(Progs.GlobalStruct.StartFrame); // // treat each object in turn // for (int i = 0; i < sv.num_edicts; i++) { edict_t ent = sv.edicts[i]; if (ent.free) { continue; } if (Progs.GlobalStruct.force_retouch != 0) { LinkEdict(ent, true); // force retouch even for stationary } if (i > 0 && i <= svs.maxclients) { Physics_Client(ent, i); } else { switch ((int)ent.v.movetype) { case Movetypes.MOVETYPE_PUSH: Physics_Pusher(ent); break; case Movetypes.MOVETYPE_NONE: Physics_None(ent); break; case Movetypes.MOVETYPE_NOCLIP: Physics_Noclip(ent); break; case Movetypes.MOVETYPE_STEP: Physics_Step(ent); break; case Movetypes.MOVETYPE_TOSS: case Movetypes.MOVETYPE_BOUNCE: case Movetypes.MOVETYPE_FLY: case Movetypes.MOVETYPE_FLYMISSILE: Physics_Toss(ent); break; default: Sys.Error("SV_Physics: bad movetype {0}", (int)ent.v.movetype); break; } } } if (Progs.GlobalStruct.force_retouch != 0) { Progs.GlobalStruct.force_retouch -= 1; } sv.time += Host.FrameTime; }
/// <summary> /// SV_PushMove /// </summary> static void PushMove(edict_t pusher, float movetime) { if (pusher.v.velocity.IsEmpty) { pusher.v.ltime += movetime; return; } v3f move, mins, maxs; Mathlib.VectorScale(ref pusher.v.velocity, movetime, out move); Mathlib.VectorAdd(ref pusher.v.absmin, ref move, out mins); Mathlib.VectorAdd(ref pusher.v.absmax, ref move, out maxs); v3f pushorig = pusher.v.origin; edict_t[] moved_edict = new edict_t[QDef.MAX_EDICTS]; v3f[] moved_from = new v3f[QDef.MAX_EDICTS]; // move the pusher to it's final position Mathlib.VectorAdd(ref pusher.v.origin, ref move, out pusher.v.origin); pusher.v.ltime += movetime; LinkEdict(pusher, false); // see if any solid entities are inside the final position int num_moved = 0; for (int e = 1; e < sv.num_edicts; e++) { edict_t check = sv.edicts[e]; if (check.free) { continue; } if (check.v.movetype == Movetypes.MOVETYPE_PUSH || check.v.movetype == Movetypes.MOVETYPE_NONE || check.v.movetype == Movetypes.MOVETYPE_NOCLIP) { continue; } // if the entity is standing on the pusher, it will definately be moved if (!(((int)check.v.flags & EdictFlags.FL_ONGROUND) != 0 && ProgToEdict(check.v.groundentity) == pusher)) { if (check.v.absmin.x >= maxs.x || check.v.absmin.y >= maxs.y || check.v.absmin.z >= maxs.z || check.v.absmax.x <= mins.x || check.v.absmax.y <= mins.y || check.v.absmax.z <= mins.z) { continue; } // see if the ent's bbox is inside the pusher's final position if (TestEntityPosition(check) == null) { continue; } } // remove the onground flag for non-players if (check.v.movetype != Movetypes.MOVETYPE_WALK) { check.v.flags = (int)check.v.flags & ~EdictFlags.FL_ONGROUND; } v3f entorig = check.v.origin; moved_from[num_moved] = entorig; moved_edict[num_moved] = check; num_moved++; // try moving the contacted entity pusher.v.solid = Solids.SOLID_NOT; PushEntity(check, ref move); pusher.v.solid = Solids.SOLID_BSP; // if it is still inside the pusher, block edict_t block = TestEntityPosition(check); if (block != null) { // fail the move if (check.v.mins.x == check.v.maxs.x) { continue; } if (check.v.solid == Solids.SOLID_NOT || check.v.solid == Solids.SOLID_TRIGGER) { // corpse check.v.mins.x = check.v.mins.y = 0; check.v.maxs = check.v.mins; continue; } check.v.origin = entorig; LinkEdict(check, true); pusher.v.origin = pushorig; LinkEdict(pusher, false); pusher.v.ltime -= movetime; // if the pusher has a "blocked" function, call it // otherwise, just stay in place until the obstacle is gone if (pusher.v.blocked != 0) { Progs.GlobalStruct.self = EdictToProg(pusher); Progs.GlobalStruct.other = EdictToProg(check); Progs.Execute(pusher.v.blocked); } // move back any entities we already moved for (int i = 0; i < num_moved; i++) { moved_edict[i].v.origin = moved_from[i]; LinkEdict(moved_edict[i], false); } return; } } }
/// <summary> /// Host_Spawn_f /// </summary> private static void Spawn_f() { if (Cmd.Source == cmd_source_t.src_command) { Con.Print("spawn is not valid from the console\n"); return; } if (Host.HostClient.spawned) { Con.Print("Spawn not valid -- allready spawned\n"); return; } edict_t ent; // run the entrance script if (Server.sv.loadgame) { // loaded games are fully inited allready // if this is the last client to be connected, unpause Server.sv.paused = false; } else { // set up the edict ent = Host.HostClient.edict; ent.Clear(); //memset(&ent.v, 0, progs.entityfields * 4); ent.v.colormap = Server.NumForEdict(ent); ent.v.team = (Host.HostClient.colors & 15) + 1; ent.v.netname = Progs.NewString(Host.HostClient.name); // copy spawn parms out of the client_t Progs.GlobalStruct.SetParams(Host.HostClient.spawn_parms); // call the spawn function Progs.GlobalStruct.time = (float)Server.sv.time; Progs.GlobalStruct.self = Server.EdictToProg(Server.Player); Progs.Execute(Progs.GlobalStruct.ClientConnect); if ((Sys.GetFloatTime() - Host.HostClient.netconnection.connecttime) <= Server.sv.time) { Con.DPrint("{0} entered the game\n", Host.HostClient.name); } Progs.Execute(Progs.GlobalStruct.PutClientInServer); } // send all current names, colors, and frag counts MsgWriter msg = Host.HostClient.message; msg.Clear(); // send time of update msg.WriteByte(Protocol.svc_time); msg.WriteFloat((float)Server.sv.time); for (int i = 0; i < Server.svs.maxclients; i++) { client_t client = Server.svs.clients[i]; msg.WriteByte(Protocol.svc_updatename); msg.WriteByte(i); msg.WriteString(client.name); msg.WriteByte(Protocol.svc_updatefrags); msg.WriteByte(i); msg.WriteShort(client.old_frags); msg.WriteByte(Protocol.svc_updatecolors); msg.WriteByte(i); msg.WriteByte(client.colors); } // send all current light styles for (int i = 0; i < QDef.MAX_LIGHTSTYLES; i++) { msg.WriteByte(Protocol.svc_lightstyle); msg.WriteByte((char)i); msg.WriteString(Server.sv.lightstyles[i]); } // // send some stats // msg.WriteByte(Protocol.svc_updatestat); msg.WriteByte(QStats.STAT_TOTALSECRETS); msg.WriteLong((int)Progs.GlobalStruct.total_secrets); msg.WriteByte(Protocol.svc_updatestat); msg.WriteByte(QStats.STAT_TOTALMONSTERS); msg.WriteLong((int)Progs.GlobalStruct.total_monsters); msg.WriteByte(Protocol.svc_updatestat); msg.WriteByte(QStats.STAT_SECRETS); msg.WriteLong((int)Progs.GlobalStruct.found_secrets); msg.WriteByte(Protocol.svc_updatestat); msg.WriteByte(QStats.STAT_MONSTERS); msg.WriteLong((int)Progs.GlobalStruct.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 ent = Server.EdictNum(1 + Host.ClientNum); msg.WriteByte(Protocol.svc_setangle); msg.WriteAngle(ent.v.angles.x); msg.WriteAngle(ent.v.angles.y); msg.WriteAngle(0); Server.WriteClientDataToMessage(Server.Player, Host.HostClient.message); msg.WriteByte(Protocol.svc_signonnum); msg.WriteByte(3); Host.HostClient.sendsignon = true; }