/// <summary> /// SV_RunClients /// </summary> public static void RunClients() { for (int i = 0; i < svs.maxclients; i++) { Host.HostClient = svs.clients[i]; if (!Host.HostClient.active) continue; _Player = Host.HostClient.edict; if (!ReadClientMessage()) { DropClient(false); // client misbehaved... continue; } if (!Host.HostClient.spawned) { // clear client movement until a new packet is received Host.HostClient.cmd.Clear(); continue; } // always pause in single player if in console or menus if (!sv.paused && (svs.maxclients > 1 || Key.Destination == keydest_t.key_game)) ClientThink(); } }
/// <summary> /// SV_CheckBottom /// </summary> public static bool CheckBottom(edict_t ent) { v3f mins, maxs; Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.mins, out mins); Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.maxs, out 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 Vector3 start; start.Z = mins.z - 1; for (int x = 0; x <= 1; x++) for (int y = 0; y <= 1; y++) { start.X = (x != 0 ? maxs.x : mins.x); start.Y = (y != 0 ? maxs.y : mins.y); if (PointContents(ref start) != Contents.CONTENTS_SOLID) goto RealCheck; } return true; // we got out easy RealCheck: // // check it for real... // start.Z = mins.z; // the midpoint must be within 16 of the bottom start.X = (mins.x + maxs.x) * 0.5f; start.Y = (mins.y + maxs.y) * 0.5f; Vector3 stop = start; stop.Z -= 2 * STEPSIZE; trace_t trace = Move(ref start, ref Common.ZeroVector, ref Common.ZeroVector, ref stop, 1, ent); if (trace.fraction == 1.0) return false; float mid = trace.endpos.Z; float bottom = mid; // the corners must be within 16 of the midpoint for (int x = 0; x <= 1; x++) for (int y = 0; y <= 1; y++) { start.X = stop.X = (x != 0 ? maxs.x : mins.x); start.Y = stop.Y = (y != 0 ? maxs.y : mins.y); trace = Move(ref start, ref Common.ZeroVector, ref Common.ZeroVector, ref stop, 1, ent); if (trace.fraction != 1.0 && trace.endpos.Z > bottom) bottom = trace.endpos.Z; if (trace.fraction == 1.0 || mid - trace.endpos.Z > STEPSIZE) return false; } return true; }
public bool startsolid; // if true, the initial point was in a solid area #endregion Fields #region Methods public void CopyFrom(trace_t src) { this.allsolid = src.allsolid; this.startsolid = src.startsolid; this.inopen = src.inopen; this.inwater = src.inwater; this.fraction = src.fraction; this.endpos = src.endpos; this.plane = src.plane; this.ent = src.ent; }
/// <summary> /// SV_Impact /// Two entities have touched, so run their touch functions /// </summary> private 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; }
/* * ================= * PF_findradius * * Returns a chain of entities that have origins within a spherical area * * findradius (origin, radius) * ================= */ static unsafe void PF_findradius() { edict_t chain = Server.sv.edicts[0]; float *org = GetVector(OFS.OFS_PARM0); float rad = GetFloat(OFS.OFS_PARM1); Vector3 vorg; Copy(org, out vorg); for (int i = 1; i < Server.sv.num_edicts; i++) { edict_t ent = Server.sv.edicts[i]; if (ent.free) { continue; } if (ent.v.solid == Solids.SOLID_NOT) { continue; } Vector3 v = vorg - (Common.ToVector(ref ent.v.origin) + (Common.ToVector(ref ent.v.mins) + Common.ToVector(ref ent.v.maxs)) * 0.5f); if (v.Length > rad) { continue; } ent.v.chain = Server.EdictToProg(chain); chain = ent; } ReturnEdict(chain); }
/// <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_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_MoveToGoal /// </summary> public static void MoveToGoal() { edict_t ent = ProgToEdict(Progs.GlobalStruct.self); edict_t goal = ProgToEdict(ent.v.goalentity); float dist = QBuiltins.GetFloat(OFS.OFS_PARM0); if (((int)ent.v.flags & (EdictFlags.FL_ONGROUND | EdictFlags.FL_FLY | EdictFlags.FL_SWIM)) == 0) { QBuiltins.ReturnFloat(0); return; } // if the next step hits the enemy, return immediately if (ProgToEdict(ent.v.enemy) != sv.edicts[0] && CloseEnough(ent, goal, dist)) { return; } // bump around... if ((Sys.Random() & 3) == 1 || !StepDirection(ent, ent.v.ideal_yaw, dist)) { NewChaseDir(ent, goal, dist); } }
/// <summary> /// SV_LinkEdict /// /// Needs to be called any time an entity changes origin, mins, maxs, or solid /// flags ent->v.modified /// sets ent->v.absmin and ent->v.absmax /// if touchtriggers, calls prog functions for the intersected triggers /// </summary> public static void LinkEdict(edict_t ent, bool touch_triggers) { if (ent.area.Prev != null) { UnlinkEdict(ent); // unlink from old position } if (ent == sv.edicts[0]) { return; // don't add the world } if (ent.free) { return; } // set the abs box Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.mins, out ent.v.absmin); Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.maxs, out ent.v.absmax); // // to make items easier to pick up and allow them to be grabbed off // of shelves, the abs sizes are expanded // if (((int)ent.v.flags & EdictFlags.FL_ITEM) != 0) { ent.v.absmin.x -= 15; ent.v.absmin.y -= 15; ent.v.absmax.x += 15; ent.v.absmax.y += 15; } else { // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch ent.v.absmin.x -= 1; ent.v.absmin.y -= 1; ent.v.absmin.z -= 1; ent.v.absmax.x += 1; ent.v.absmax.y += 1; ent.v.absmax.z += 1; } // link to PVS leafs ent.num_leafs = 0; if (ent.v.modelindex != 0) { FindTouchedLeafs(ent, sv.worldmodel.nodes[0]); } if (ent.v.solid == Solids.SOLID_NOT) { return; } // find the first node that the ent's box crosses areanode_t node = _AreaNodes[0]; while (true) { if (node.axis == -1) { break; } if (Mathlib.Comp(ref ent.v.absmin, node.axis) > node.dist) { node = node.children[0]; } else if (Mathlib.Comp(ref ent.v.absmax, node.axis) < node.dist) { node = node.children[1]; } else { break; // crosses the node } } // link it in if (ent.v.solid == Solids.SOLID_TRIGGER) { ent.area.InsertBefore(node.trigger_edicts); } else { ent.area.InsertBefore(node.solid_edicts); } // if touch_triggers, touch all entities at this node and decend for more if (touch_triggers) { TouchLinks(ent, _AreaNodes[0]); } }
/// <summary> /// SV_TouchLinks /// </summary> static void TouchLinks(edict_t ent, areanode_t node) { // touch linked edicts link_t next; for (link_t 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_HullForEntity /// Returns a hull that can be used for testing or clipping an object of mins/maxs size. /// Offset is filled in to contain the adjustment that must be added to the /// testing object's origin to get a point to use with the returned hull. /// </summary> static hull_t HullForEntity(edict_t ent, ref Vector3 mins, ref Vector3 maxs, out Vector3 offset) { hull_t hull = null; // decide which clipping hull to use, based on the size if (ent.v.solid == Solids.SOLID_BSP) { // explicit hulls in the BSP model if (ent.v.movetype != Movetypes.MOVETYPE_PUSH) Sys.Error("SOLID_BSP without MOVETYPE_PUSH"); model_t model = sv.models[(int)ent.v.modelindex]; if (model == null || model.type != modtype_t.mod_brush) Sys.Error("MOVETYPE_PUSH with a non bsp model"); Vector3 size = maxs - mins; if (size.X < 3) hull = model.hulls[0]; else if (size.X <= 32) hull = model.hulls[1]; else hull = model.hulls[2]; // calculate an offset value to center the origin offset = hull.clip_mins - mins; offset += Common.ToVector(ref ent.v.origin); } else { // create a temp hull from bounding box sizes Vector3 hullmins = Common.ToVector(ref ent.v.mins) - maxs; Vector3 hullmaxs = Common.ToVector(ref ent.v.maxs) - mins; hull = HullForBox(ref hullmins, ref hullmaxs); offset = Common.ToVector(ref ent.v.origin); } return hull; }
static trace_t Move(ref v3f start, ref v3f mins, ref v3f maxs, ref v3f end, int type, edict_t passedict) { Vector3 vstart, vmins, vmaxs, vend; Mathlib.Copy(ref start, out vstart); Mathlib.Copy(ref mins, out vmins); Mathlib.Copy(ref maxs, out vmaxs); Mathlib.Copy(ref end, out vend); return Move(ref vstart, ref vmins, ref vmaxs, ref vend, type, passedict); }
/// <summary> /// SV_Physics_None /// Non moving objects can only think /// </summary> static void Physics_None(edict_t ent) { // regular thinking RunThink(ent); }
/// <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> /// 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) /// </summary> public static void StartSound(edict_t entity, int channel, string sample, int volume, float attenuation) { if (volume < 0 || volume > 255) { sys.Error("SV_StartSound: volume = {0}", volume); } if (attenuation < 0 || attenuation > 4) { sys.Error("SV_StartSound: attenuation = {0}", attenuation); } if (channel < 0 || channel > 7) { sys.Error("SV_StartSound: channel = {0}", channel); } if (sv.datagram.Length > QDef.MAX_DATAGRAM - 16) { return; } // find precache number for sound int sound_num; for (sound_num = 1; sound_num < QDef.MAX_SOUNDS && sv.sound_precache[sound_num] != null; sound_num++) { if (sample == sv.sound_precache[sound_num]) { break; } } if (sound_num == QDef.MAX_SOUNDS || String.IsNullOrEmpty(sv.sound_precache[sound_num])) { Con.Print("SV_StartSound: {0} not precacheed\n", sample); return; } int ent = NumForEdict(entity); channel = (ent << 3) | channel; int field_mask = 0; if (volume != snd.DEFAULT_SOUND_PACKET_VOLUME) { field_mask |= protocol.SND_VOLUME; } if (attenuation != snd.DEFAULT_SOUND_PACKET_ATTENUATION) { field_mask |= protocol.SND_ATTENUATION; } // directed messages go only to the entity the are targeted on sv.datagram.WriteByte(protocol.svc_sound); sv.datagram.WriteByte(field_mask); if ((field_mask & protocol.SND_VOLUME) != 0) { sv.datagram.WriteByte(volume); } if ((field_mask & protocol.SND_ATTENUATION) != 0) { sv.datagram.WriteByte((int)(attenuation * 64)); } sv.datagram.WriteShort(channel); sv.datagram.WriteByte(sound_num); v3f v; mathlib.VectorAdd(ref entity.v.mins, ref entity.v.maxs, out v); mathlib.VectorMA(ref entity.v.origin, 0.5f, ref v, out v); sv.datagram.WriteCoord(v.x); sv.datagram.WriteCoord(v.y); sv.datagram.WriteCoord(v.z); }
/// <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_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> /// PushEntity /// Does not change the entities velocity at all /// </summary> private static trace_t PushEntity(edict_t ent, ref v3f push) { v3f end; Mathlib.VectorAdd(ref ent.v.origin, ref push, out end); trace_t trace; if (ent.v.movetype == Movetypes.MOVETYPE_FLYMISSILE) trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref end, MOVE_MISSILE, ent); else if (ent.v.solid == Solids.SOLID_TRIGGER || ent.v.solid == Solids.SOLID_NOT) // only clip against bmodels trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref end, MOVE_NOMONSTERS, ent); else trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref end, MOVE_NORMAL, ent); Mathlib.Copy(ref trace.endpos, out ent.v.origin); LinkEdict(ent, true); if (trace.ent != null) Impact(ent, trace.ent); return trace; }
/// <summary> /// SV_Physics_Toss /// Toss, bounce, and fly movement. When onground, do nothing. /// </summary> private static void Physics_Toss(edict_t ent) { // regular thinking if (!RunThink(ent)) return; // if onground, return without moving if (((int)ent.v.flags & EdictFlags.FL_ONGROUND) != 0) return; CheckVelocity(ent); // add gravity if (ent.v.movetype != Movetypes.MOVETYPE_FLY && ent.v.movetype != Movetypes.MOVETYPE_FLYMISSILE) AddGravity(ent); // move angles Mathlib.VectorMA(ref ent.v.angles, (float)Host.FrameTime, ref ent.v.avelocity, out ent.v.angles); // move origin v3f move; Mathlib.VectorScale(ref ent.v.velocity, (float)Host.FrameTime, out move); trace_t trace = PushEntity(ent, ref move); if (trace.fraction == 1) return; if (ent.free) return; float backoff; if (ent.v.movetype == Movetypes. MOVETYPE_BOUNCE) backoff = 1.5f; else backoff = 1; ClipVelocity(ref ent.v.velocity, ref trace.plane.normal, out ent.v.velocity, backoff); // stop if on ground if (trace.plane.normal.Z > 0.7f) { if (ent.v.velocity.z < 60 || ent.v.movetype != Movetypes. MOVETYPE_BOUNCE) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(trace.ent); ent.v.velocity = default(v3f); ent.v.avelocity = default(v3f); } } // check for in water CheckWaterTransition(ent); }
/// <summary> /// SV_Physics_Step /// </summary> static void Physics_Step(edict_t ent) { bool hitsound; // freefall if not onground if (((int)ent.v.flags & (EdictFlags.FL_ONGROUND | EdictFlags.FL_FLY | EdictFlags.FL_SWIM)) == 0) { if (ent.v.velocity.z < _Gravity.Value * -0.1) hitsound = true; else hitsound = false; AddGravity(ent); CheckVelocity(ent); FlyMove(ent, (float)Host.FrameTime, null); LinkEdict(ent, true); if (((int)ent.v.flags & EdictFlags.FL_ONGROUND) != 0) // just hit ground { if (hitsound) StartSound(ent, 0, "demon/dland2.wav", 255, 1); } } // regular thinking RunThink(ent); CheckWaterTransition(ent); }
/// <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_ClipToLinks /// Mins and maxs enclose the entire area swept by the move /// </summary> static void ClipToLinks(areanode_t node, moveclip_t clip) { link_t next; trace_t trace; // touch linked edicts for (link_t l = node.solid_edicts.Next; l != node.solid_edicts; l = next) { next = l.Next; edict_t touch = (edict_t)l.Owner;// EDICT_FROM_AREA(l); if (touch.v.solid == Solids.SOLID_NOT) { continue; } if (touch == clip.passedict) { continue; } if (touch.v.solid == Solids.SOLID_TRIGGER) { Sys.Error("Trigger in clipping list"); } if (clip.type == MOVE_NOMONSTERS && touch.v.solid != Solids.SOLID_BSP) { continue; } if (clip.boxmins.X > touch.v.absmax.x || clip.boxmins.Y > touch.v.absmax.y || clip.boxmins.Z > touch.v.absmax.z || clip.boxmaxs.X < touch.v.absmin.x || clip.boxmaxs.Y < touch.v.absmin.y || clip.boxmaxs.Z < touch.v.absmin.z) { continue; } if (clip.passedict != null && clip.passedict.v.size.x != 0 && touch.v.size.x == 0) { continue; // points never interact } // might intersect, so do an exact clip if (clip.trace.allsolid) { return; } if (clip.passedict != null) { if (ProgToEdict(touch.v.owner) == clip.passedict) { continue; // don't clip against own missiles } if (ProgToEdict(clip.passedict.v.owner) == touch) { continue; // don't clip against owner } } if (((int)touch.v.flags & EdictFlags.FL_MONSTER) != 0) { trace = ClipMoveToEntity(touch, ref clip.start, ref clip.mins2, ref clip.maxs2, ref clip.end); } else { trace = ClipMoveToEntity(touch, ref clip.start, ref clip.mins, ref clip.maxs, ref clip.end); } if (trace.allsolid || trace.startsolid || trace.fraction < clip.trace.fraction) { trace.ent = touch; if (clip.trace.startsolid) { clip.trace = trace; clip.trace.startsolid = true; } else { clip.trace = trace; } } else if (trace.startsolid) { clip.trace.startsolid = true; } } // recurse down both sides if (node.axis == -1) { return; } if (Mathlib.Comp(ref clip.boxmaxs, node.axis) > node.dist) { ClipToLinks(node.children[0], clip); } if (Mathlib.Comp(ref clip.boxmins, node.axis) < node.dist) { ClipToLinks(node.children[1], clip); } }
/// <summary> /// 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. /// </summary> public static string ParseEdict(string data, edict_t ent) { bool init = false; // clear it if (ent != Server.sv.edicts[0]) // hack { ent.Clear(); } // go through all the dictionary pairs bool anglehack; while (true) { // parse key data = Common.Parse(data); if (Common.Token.StartsWith("}")) { break; } if (data == null) { Sys.Error("ED_ParseEntity: EOF without closing brace"); } string token = Common.Token; // anglehack is to allow QuakeEd to write single scalar angles // and allow them to be turned into vectors. (FIXME...) if (token == "angle") { token = "angles"; anglehack = true; } else { anglehack = false; } // FIXME: change light to _light to get rid of this hack if (token == "light") { token = "light_lev"; // hack for single light def } string keyname = token.TrimEnd(); // parse value data = Common.Parse(data); if (data == null) { Sys.Error("ED_ParseEntity: EOF without closing brace"); } if (Common.Token.StartsWith("}")) { Sys.Error("ED_ParseEntity: closing brace without data"); } init = true; // keynames with a leading underscore are used for utility comments, // and are immediately discarded by quake if (keyname[0] == '_') { continue; } ddef_t key = FindField(keyname); if (key == null) { Con.Print("'{0}' is not a field\n", keyname); continue; } token = Common.Token; if (anglehack) { token = "0 " + token + " 0"; } if (!ParsePair(ent, key, token)) { Host.Error("ED_ParseEdict: parse error"); } } if (!init) { ent.free = true; } return(data); }
/// <summary> /// 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... /// </summary> static int TryUnstick(edict_t ent, ref v3f oldvel) { v3f oldorg = ent.v.origin; v3f dir = Common.ZeroVector3f; trace_t steptrace = new trace_t(); for (int i = 0; i < 8; i++) { // try pushing a little in an axial direction switch (i) { case 0: dir.x = 2; dir.y = 0; break; case 1: dir.x = 0; dir.y = 2; break; case 2: dir.x = -2; dir.y = 0; break; case 3: dir.x = 0; dir.y = -2; break; case 4: dir.x = 2; dir.y = 2; break; case 5: dir.x = -2; dir.y = 2; break; case 6: dir.x = 2; dir.y = -2; break; case 7: dir.x = -2; dir.y = -2; break; } PushEntity(ent, ref dir); // retry the original move ent.v.velocity.x = oldvel.x; ent.v.velocity.y = oldvel.y; ent.v.velocity.z = 0; int clip = FlyMove(ent, 0.1f, steptrace); if (Math.Abs(oldorg.y - ent.v.origin.y) > 4 || Math.Abs(oldorg.x - ent.v.origin.x) > 4) { return clip; } // go back to the original pos and try again ent.v.origin = oldorg; } ent.v.velocity = Common.ZeroVector3f; return 7; // still not moving }
/// <summary> /// SV_WriteEntitiesToClient /// </summary> private static void WriteEntitiesToClient(edict_t clent, MsgWriter msg) { // find the client's PVS Vector3 org = common.ToVector(ref clent.v.origin) + common.ToVector(ref clent.v.view_ofs); byte[] pvs = FatPVS(ref org); // send over all entities (except the client) that touch the pvs for (int e = 1; e < sv.num_edicts; e++) { edict_t ent = sv.edicts[e]; // ignore if not touching a PV leaf if (ent != clent) // clent is ALLWAYS sent { // ignore ents without visible models string mname = progs.GetString(ent.v.model); if (String.IsNullOrEmpty(mname)) { continue; } int i; for (i = 0; i < ent.num_leafs; i++) { if ((pvs[ent.leafnums[i] >> 3] & (1 << (ent.leafnums[i] & 7))) != 0) { break; } } if (i == ent.num_leafs) { continue; // not visible } } if (msg.Capacity - msg.Length < 16) { Con.Print("packet overflow\n"); return; } // send an update int bits = 0; v3f miss; mathlib.VectorSubtract(ref ent.v.origin, ref ent.baseline.origin, out miss); if (miss.x < -0.1f || miss.x > 0.1f) { bits |= protocol.U_ORIGIN1; } if (miss.y < -0.1f || miss.y > 0.1f) { bits |= protocol.U_ORIGIN2; } if (miss.z < -0.1f || miss.z > 0.1f) { bits |= protocol.U_ORIGIN3; } if (ent.v.angles.x != ent.baseline.angles.x) { bits |= protocol.U_ANGLE1; } if (ent.v.angles.y != ent.baseline.angles.y) { bits |= protocol.U_ANGLE2; } if (ent.v.angles.z != ent.baseline.angles.z) { bits |= protocol.U_ANGLE3; } if (ent.v.movetype == Movetypes.MOVETYPE_STEP) { bits |= protocol.U_NOLERP; // don't mess up the step animation } if (ent.baseline.colormap != ent.v.colormap) { bits |= protocol.U_COLORMAP; } if (ent.baseline.skin != ent.v.skin) { bits |= protocol.U_SKIN; } if (ent.baseline.frame != ent.v.frame) { bits |= protocol.U_FRAME; } if (ent.baseline.effects != ent.v.effects) { bits |= protocol.U_EFFECTS; } if (ent.baseline.modelindex != ent.v.modelindex) { bits |= protocol.U_MODEL; } if (e >= 256) { bits |= protocol.U_LONGENTITY; } if (bits >= 256) { bits |= protocol.U_MOREBITS; } // // write the message // msg.WriteByte(bits | protocol.U_SIGNAL); if ((bits & protocol.U_MOREBITS) != 0) { msg.WriteByte(bits >> 8); } if ((bits & protocol.U_LONGENTITY) != 0) { msg.WriteShort(e); } else { msg.WriteByte(e); } if ((bits & protocol.U_MODEL) != 0) { msg.WriteByte((int)ent.v.modelindex); } if ((bits & protocol.U_FRAME) != 0) { msg.WriteByte((int)ent.v.frame); } if ((bits & protocol.U_COLORMAP) != 0) { msg.WriteByte((int)ent.v.colormap); } if ((bits & protocol.U_SKIN) != 0) { msg.WriteByte((int)ent.v.skin); } if ((bits & protocol.U_EFFECTS) != 0) { msg.WriteByte((int)ent.v.effects); } if ((bits & protocol.U_ORIGIN1) != 0) { msg.WriteCoord(ent.v.origin.x); } if ((bits & protocol.U_ANGLE1) != 0) { msg.WriteAngle(ent.v.angles.x); } if ((bits & protocol.U_ORIGIN2) != 0) { msg.WriteCoord(ent.v.origin.y); } if ((bits & protocol.U_ANGLE2) != 0) { msg.WriteAngle(ent.v.angles.y); } if ((bits & protocol.U_ORIGIN3) != 0) { msg.WriteCoord(ent.v.origin.z); } if ((bits & protocol.U_ANGLE3) != 0) { msg.WriteAngle(ent.v.angles.z); } } }
/// <summary> /// SV_WalkMove /// Only used by players /// </summary> static void WalkMove(edict_t ent) { // // do a regular slide move unless it looks like you ran into a step // int oldonground = (int)ent.v.flags & EdictFlags.FL_ONGROUND; ent.v.flags = (int)ent.v.flags & ~EdictFlags.FL_ONGROUND; v3f oldorg = ent.v.origin; v3f oldvel = ent.v.velocity; trace_t steptrace = new trace_t(); int clip = FlyMove(ent, (float)Host.FrameTime, steptrace); if ((clip & 2) == 0) return; // move didn't block on a step if (oldonground == 0 && ent.v.waterlevel == 0) return; // don't stair up while jumping if (ent.v.movetype != Movetypes.MOVETYPE_WALK) return; // gibbed by a trigger if (_NoStep.Value != 0) return; if (((int)_Player.v.flags & EdictFlags.FL_WATERJUMP) != 0) return; v3f nosteporg = ent.v.origin; v3f nostepvel = ent.v.velocity; // // try moving up and forward to go up a step // ent.v.origin = oldorg; // back to start pos v3f upmove = Common.ZeroVector3f; v3f downmove = upmove; upmove.z = STEPSIZE; downmove.z = (float)(-STEPSIZE + oldvel.z * Host.FrameTime); // move up PushEntity(ent, ref upmove); // FIXME: don't link? // move forward ent.v.velocity.x = oldvel.x; ent.v.velocity.y = oldvel.y; ent.v.velocity.z = 0; clip = FlyMove(ent, (float)Host.FrameTime, steptrace); // check for stuckness, possibly due to the limited precision of floats // in the clipping hulls if (clip != 0) { if (Math.Abs(oldorg.y - ent.v.origin.y) < 0.03125 && Math.Abs(oldorg.x - ent.v.origin.x) < 0.03125) { // stepping up didn't make any progress clip = TryUnstick(ent, ref oldvel); } } // extra friction based on view angle if ((clip & 2) != 0) WallFriction(ent, steptrace); // move down trace_t downtrace = PushEntity(ent, ref downmove); // FIXME: don't link? if (downtrace.plane.normal.Z > 0.7) { if (ent.v.solid == Solids.SOLID_BSP) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(downtrace.ent); } } else { // 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 ent.v.origin = nosteporg; ent.v.velocity = nostepvel; } }
/// <summary> /// SV_WallFriction /// </summary> static void WallFriction(edict_t ent, trace_t trace) { Vector3 forward, right, up, vangle = Common.ToVector(ref ent.v.v_angle); Mathlib.AngleVectors(ref vangle, out forward, out right, out up); float d = Vector3.Dot(trace.plane.normal, forward); d += 0.5f; if (d >= 0) return; // cut the tangential velocity Vector3 vel = Common.ToVector(ref ent.v.velocity); float i = Vector3.Dot(trace.plane.normal, vel); Vector3 into = trace.plane.normal * i; Vector3 side = vel - into; ent.v.velocity.x = side.X * (1 + d); ent.v.velocity.y = side.Y * (1 + d); }
/// <summary> /// ED_Free /// Marks the edict as free /// FIXME: walk all entities and NULL out references to this entity /// </summary> public static void FreeEdict(edict_t ed) { UnlinkEdict(ed); // unlink from world bsp ed.free = true; ed.v.model = 0; ed.v.takedamage = 0; ed.v.modelindex = 0; ed.v.colormap = 0; ed.v.skin = 0; ed.v.frame = 0; ed.v.origin = default(v3f); ed.v.angles = default(v3f); ed.v.nextthink = -1; ed.v.solid = 0; ed.freetime = (float)sv.time; }
/// <summary> /// SV_FixCheckBottom /// </summary> static void FixCheckBottom(edict_t ent) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_PARTIALGROUND; }
/// <summary> /// SV_FindTouchedLeafs /// </summary> static void FindTouchedLeafs(edict_t ent, mnodebase_t node) { if (node.contents == Contents.CONTENTS_SOLID) return; // add an efrag if the node is a leaf if (node.contents < 0) { if (ent.num_leafs == Progs.MAX_ENT_LEAFS) return; mleaf_t leaf = (mleaf_t)node; int leafnum = Array.IndexOf(sv.worldmodel.leafs, leaf) - 1; ent.leafnums[ent.num_leafs] = (short)leafnum; ent.num_leafs++; return; } // NODE_MIXED mnode_t n = (mnode_t)node; mplane_t splitplane = n.plane; int sides = Mathlib.BoxOnPlaneSide(ref ent.v.absmin, ref ent.v.absmax, splitplane); // recurse down the contacted sides if ((sides & 1) != 0) FindTouchedLeafs(ent, n.children[0]); if ((sides & 2) != 0) FindTouchedLeafs(ent, n.children[1]); }
/// <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_TestEntityPosition /// This could be a lot more efficient... /// </summary> static edict_t TestEntityPosition(edict_t ent) { trace_t trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref ent.v.origin, 0, ent); if (trace.startsolid) return sv.edicts[0]; return null; }
static void PF_Spawn() { edict_t ed = Server.AllocEdict(); ReturnEdict(ed); }
/// <summary> /// SV_LinkEdict /// /// Needs to be called any time an entity changes origin, mins, maxs, or solid /// flags ent->v.modified /// sets ent->v.absmin and ent->v.absmax /// if touchtriggers, calls prog functions for the intersected triggers /// </summary> public static void LinkEdict(edict_t ent, bool touch_triggers) { if (ent.area.Prev != null) UnlinkEdict(ent); // unlink from old position if (ent == sv.edicts[0]) return; // don't add the world if (ent.free) return; // set the abs box Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.mins, out ent.v.absmin); Mathlib.VectorAdd(ref ent.v.origin, ref ent.v.maxs, out ent.v.absmax); // // to make items easier to pick up and allow them to be grabbed off // of shelves, the abs sizes are expanded // if (((int)ent.v.flags & EdictFlags.FL_ITEM) != 0) { ent.v.absmin.x -= 15; ent.v.absmin.y -= 15; ent.v.absmax.x += 15; ent.v.absmax.y += 15; } else { // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch ent.v.absmin.x -= 1; ent.v.absmin.y -= 1; ent.v.absmin.z -= 1; ent.v.absmax.x += 1; ent.v.absmax.y += 1; ent.v.absmax.z += 1; } // link to PVS leafs ent.num_leafs = 0; if (ent.v.modelindex != 0) FindTouchedLeafs(ent, sv.worldmodel.nodes[0]); if (ent.v.solid == Solids.SOLID_NOT) return; // find the first node that the ent's box crosses areanode_t node = _AreaNodes[0]; while (true) { if (node.axis == -1) break; if (Mathlib.Comp(ref ent.v.absmin, node.axis) > node.dist) node = node.children[0]; else if (Mathlib.Comp(ref ent.v.absmax, node.axis) < node.dist) node = node.children[1]; else break; // crosses the node } // link it in if (ent.v.solid == Solids.SOLID_TRIGGER) ent.area.InsertBefore(node.trigger_edicts); else ent.area.InsertBefore(node.solid_edicts); // if touch_triggers, touch all entities at this node and decend for more if (touch_triggers) TouchLinks(ent, _AreaNodes[0]); }
static void PF_Remove() { edict_t ed = GetEdict(OFS.OFS_PARM0); Server.FreeEdict(ed); }
/// <summary> /// SV_Move /// mins and maxs are relative /// if the entire move stays in a solid volume, trace.allsolid will be set /// if the starting point is in a solid, it will be allowed to move out to an open area /// nomonsters is used for line of sight or edge testing, where mosnters /// shouldn't be considered solid objects /// passedict is explicitly excluded from clipping checks (normally NULL) /// </summary> public static trace_t Move(ref Vector3 start, ref Vector3 mins, ref Vector3 maxs, ref Vector3 end, int type, edict_t passedict) { moveclip_t clip = new moveclip_t(); // clip to world clip.trace = ClipMoveToEntity(sv.edicts[0], ref start, ref mins, ref maxs, ref end); clip.start = start; clip.end = end; clip.mins = mins; clip.maxs = maxs; clip.type = type; clip.passedict = passedict; if (type == MOVE_MISSILE) { clip.mins2 = Vector3.One * -15; clip.maxs2 = Vector3.One * 15; } else { clip.mins2 = mins; clip.maxs2 = maxs; } // create the bounding box of the entire move MoveBounds(ref start, ref clip.mins2, ref clip.maxs2, ref end, out clip.boxmins, out clip.boxmaxs); // clip to entities ClipToLinks(_AreaNodes[0], clip); return(clip.trace); }
/// <summary> /// PF_checkbottom /// </summary> static void PF_checkbottom() { edict_t ent = GetEdict(OFS.OFS_PARM0); ReturnFloat(Server.CheckBottom(ent) ? 1 : 0); }
/// <summary> /// ED_LoadFromFile /// The entities are directly placed in the array, rather than allocated with /// 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 ED_CallSpawnFunctions () to let the objects initialize themselves. /// </summary> public static void LoadFromFile(string data) { edict_t ent = null; int inhibit = 0; Progs.GlobalStruct.time = (float)Server.sv.time; // parse ents while (true) { // parse the opening brace data = Common.Parse(data); if (data == null) { break; } if (Common.Token != "{") { Sys.Error("ED_LoadFromFile: found {0} when expecting {", Common.Token); } if (ent == null) { ent = Server.EdictNum(0); } else { ent = Server.AllocEdict(); } data = ParseEdict(data, ent); // remove things from different skill levels or deathmatch if (Host.Deathmatch != 0) { if (((int)ent.v.spawnflags & SpawnFlags.SPAWNFLAG_NOT_DEATHMATCH) != 0) { Server.FreeEdict(ent); inhibit++; continue; } } else if ((Host.CurrentSkill == 0 && ((int)ent.v.spawnflags & SpawnFlags.SPAWNFLAG_NOT_EASY) != 0) || (Host.CurrentSkill == 1 && ((int)ent.v.spawnflags & SpawnFlags.SPAWNFLAG_NOT_MEDIUM) != 0) || (Host.CurrentSkill >= 2 && ((int)ent.v.spawnflags & SpawnFlags.SPAWNFLAG_NOT_HARD) != 0)) { Server.FreeEdict(ent); inhibit++; continue; } // // immediately call spawn function // if (ent.v.classname == 0) { Con.Print("No classname for:\n"); Print(ent); Server.FreeEdict(ent); continue; } // look for the spawn function int func = IndexOfFunction(GetString(ent.v.classname)); if (func == -1) { Con.Print("No spawn function for:\n"); Print(ent); Server.FreeEdict(ent); continue; } Progs.GlobalStruct.self = Server.EdictToProg(ent); Execute(func); } Con.DPrint("{0} entities inhibited\n", inhibit); }
/* * ============= * PF_aim * * Pick a vector for the player to shoot along * vector aim(entity, missilespeed) * ============= */ static void PF_aim() { edict_t ent = GetEdict(OFS.OFS_PARM0); float speed = GetFloat(OFS.OFS_PARM1); Vector3 start = Common.ToVector(ref ent.v.origin); start.Z += 20; // try sending a trace straight Vector3 dir; Mathlib.Copy(ref Progs.GlobalStruct.v_forward, out dir); Vector3 end = start + dir * 2048; trace_t tr = Server.Move(ref start, ref Common.ZeroVector, ref Common.ZeroVector, ref end, 0, ent); if (tr.ent != null && tr.ent.v.takedamage == Damages.DAMAGE_AIM && (Host.TeamPlay == 0 || ent.v.team <= 0 || ent.v.team != tr.ent.v.team)) { ReturnVector(ref Progs.GlobalStruct.v_forward); return; } // try all possible entities Vector3 bestdir = dir; float bestdist = Server.Aim; edict_t bestent = null; for (int i = 1; i < Server.sv.num_edicts; i++) { edict_t check = Server.sv.edicts[i]; if (check.v.takedamage != Damages.DAMAGE_AIM) { continue; } if (check == ent) { continue; } if (Host.TeamPlay != 0 && ent.v.team > 0 && ent.v.team == check.v.team) { continue; // don't aim at teammate } v3f tmp; Mathlib.VectorAdd(ref check.v.mins, ref check.v.maxs, out tmp); Mathlib.VectorMA(ref check.v.origin, 0.5f, ref tmp, out tmp); Mathlib.Copy(ref tmp, out end); dir = end - start; Mathlib.Normalize(ref dir); float dist = Vector3.Dot(dir, Common.ToVector(ref Progs.GlobalStruct.v_forward)); if (dist < bestdist) { continue; // to far to turn } tr = Server.Move(ref start, ref Common.ZeroVector, ref Common.ZeroVector, ref end, 0, ent); if (tr.ent == check) { // can shoot at this one bestdist = dist; bestent = check; } } if (bestent != null) { v3f dir2, end2; Mathlib.VectorSubtract(ref bestent.v.origin, ref ent.v.origin, out dir2); float dist = Mathlib.DotProduct(ref dir2, ref Progs.GlobalStruct.v_forward); Mathlib.VectorScale(ref Progs.GlobalStruct.v_forward, dist, out end2); end2.z = dir2.z; Mathlib.Normalize(ref end2); ReturnVector(ref end2); } else { ReturnVector(ref bestdir); } }
/// <summary> /// Host_Loadgame_f /// </summary> private static void Loadgame_f() { if (Cmd.Source != cmd_source_t.src_command) { return; } if (Cmd.Argc != 2) { Con.Print("load <savename> : load a game\n"); return; } Client.cls.demonum = -1; // stop demo loop in case this fails string name = Path.ChangeExtension(Path.Combine(Common.GameDir, Cmd.Argv(1)), ".sav"); // we can't call SCR_BeginLoadingPlaque, because too much stack space has // been used. The menu calls it before stuffing loadgame command // SCR_BeginLoadingPlaque (); Con.Print("Loading game from {0}...\n", name); FileStream fs = Sys.FileOpenRead(name); if (fs == null) { Con.Print("ERROR: couldn't open.\n"); return; } using (StreamReader reader = new StreamReader(fs, Encoding.ASCII)) { string line = reader.ReadLine(); int version = Common.atoi(line); if (version != SAVEGAME_VERSION) { Con.Print("Savegame is version {0}, not {1}\n", version, SAVEGAME_VERSION); return; } line = reader.ReadLine(); float[] spawn_parms = new float[Server.NUM_SPAWN_PARMS]; for (int i = 0; i < spawn_parms.Length; i++) { line = reader.ReadLine(); spawn_parms[i] = Common.atof(line); } // this silliness is so we can load 1.06 save files, which have float skill values line = reader.ReadLine(); float tfloat = Common.atof(line); Host.CurrentSkill = (int)(tfloat + 0.1); Cvar.Set("skill", (float)Host.CurrentSkill); string mapname = reader.ReadLine(); line = reader.ReadLine(); float time = Common.atof(line); Client.Disconnect_f(); Server.SpawnServer(mapname); if (!Server.sv.active) { Con.Print("Couldn't load map\n"); return; } Server.sv.paused = true; // pause until all clients connect Server.sv.loadgame = true; // load the light styles for (int i = 0; i < QDef.MAX_LIGHTSTYLES; i++) { line = reader.ReadLine(); Server.sv.lightstyles[i] = line; } // load the edicts out of the savegame file int entnum = -1; // -1 is the globals StringBuilder sb = new StringBuilder(32768); while (!reader.EndOfStream) { line = reader.ReadLine(); if (line == null) { Sys.Error("EOF without closing brace"); } sb.AppendLine(line); int idx = line.IndexOf('}'); if (idx != -1) { int length = 1 + sb.Length - (line.Length - idx); string data = Common.Parse(sb.ToString(0, length)); if (String.IsNullOrEmpty(Common.Token)) { break; // end of file } if (Common.Token != "{") { Sys.Error("First token isn't a brace"); } if (entnum == -1) { // parse the global vars Progs.ParseGlobals(data); } else { // parse an edict edict_t ent = Server.EdictNum(entnum); ent.Clear(); Progs.ParseEdict(data, ent); // link it into the bsp tree if (!ent.free) { Server.LinkEdict(ent, false); } } entnum++; sb.Remove(0, length); } } Server.sv.num_edicts = entnum; Server.sv.time = time; for (int i = 0; i < Server.NUM_SPAWN_PARMS; i++) { Server.svs.clients[0].spawn_parms[i] = spawn_parms[i]; } } if (Client.cls.state != cactive_t.ca_dedicated) { Client.EstablishConnection("local"); Reconnect_f(); } }
/// <summary> /// RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) /// </summary> public static unsafe void ReturnEdict(edict_t e) { int prog = Server.EdictToProg(e); ReturnInt(prog); }
/// <summary> /// SV_WriteClientdataToMessage /// </summary> public static void WriteClientDataToMessage(edict_t ent, MsgWriter msg) { // // send a damage message // if (ent.v.dmg_take != 0 || ent.v.dmg_save != 0) { edict_t other = ProgToEdict(ent.v.dmg_inflictor); msg.WriteByte(protocol.svc_damage); msg.WriteByte((int)ent.v.dmg_save); msg.WriteByte((int)ent.v.dmg_take); msg.WriteCoord(other.v.origin.x + 0.5f * (other.v.mins.x + other.v.maxs.x)); msg.WriteCoord(other.v.origin.y + 0.5f * (other.v.mins.y + other.v.maxs.y)); msg.WriteCoord(other.v.origin.z + 0.5f * (other.v.mins.z + other.v.maxs.z)); ent.v.dmg_take = 0; ent.v.dmg_save = 0; } // // send the current viewpos offset from the view entity // SetIdealPitch(); // how much to look up / down ideally // a fixangle might get lost in a dropped packet. Oh well. if (ent.v.fixangle != 0) { msg.WriteByte(protocol.svc_setangle); msg.WriteAngle(ent.v.angles.x); msg.WriteAngle(ent.v.angles.y); msg.WriteAngle(ent.v.angles.z); ent.v.fixangle = 0; } int bits = 0; if (ent.v.view_ofs.z != protocol.DEFAULT_VIEWHEIGHT) { bits |= protocol.SU_VIEWHEIGHT; } if (ent.v.idealpitch != 0) { bits |= protocol.SU_IDEALPITCH; } // stuff the sigil bits into the high bits of items for sbar, or else // mix in items2 float val = progs.GetEdictFieldFloat(ent, "items2", 0); int items; if (val != 0) { items = (int)ent.v.items | ((int)val << 23); } else { items = (int)ent.v.items | ((int)progs.GlobalStruct.serverflags << 28); } bits |= protocol.SU_ITEMS; if (((int)ent.v.flags & EdictFlags.FL_ONGROUND) != 0) { bits |= protocol.SU_ONGROUND; } if (ent.v.waterlevel >= 2) { bits |= protocol.SU_INWATER; } if (ent.v.punchangle.x != 0) { bits |= protocol.SU_PUNCH1; } if (ent.v.punchangle.y != 0) { bits |= protocol.SU_PUNCH2; } if (ent.v.punchangle.z != 0) { bits |= protocol.SU_PUNCH3; } if (ent.v.velocity.x != 0) { bits |= protocol.SU_VELOCITY1; } if (ent.v.velocity.y != 0) { bits |= protocol.SU_VELOCITY2; } if (ent.v.velocity.z != 0) { bits |= protocol.SU_VELOCITY3; } if (ent.v.weaponframe != 0) { bits |= protocol.SU_WEAPONFRAME; } if (ent.v.armorvalue != 0) { bits |= protocol.SU_ARMOR; } // if (ent.v.weapon) bits |= protocol.SU_WEAPON; // send the data msg.WriteByte(protocol.svc_clientdata); msg.WriteShort(bits); if ((bits & protocol.SU_VIEWHEIGHT) != 0) { msg.WriteChar((int)ent.v.view_ofs.z); } if ((bits & protocol.SU_IDEALPITCH) != 0) { msg.WriteChar((int)ent.v.idealpitch); } if ((bits & protocol.SU_PUNCH1) != 0) { msg.WriteChar((int)ent.v.punchangle.x); } if ((bits & protocol.SU_VELOCITY1) != 0) { msg.WriteChar((int)(ent.v.velocity.x / 16)); } if ((bits & protocol.SU_PUNCH2) != 0) { msg.WriteChar((int)ent.v.punchangle.y); } if ((bits & protocol.SU_VELOCITY2) != 0) { msg.WriteChar((int)(ent.v.velocity.y / 16)); } if ((bits & protocol.SU_PUNCH3) != 0) { msg.WriteChar((int)ent.v.punchangle.z); } if ((bits & protocol.SU_VELOCITY3) != 0) { msg.WriteChar((int)(ent.v.velocity.z / 16)); } // always sent msg.WriteLong(items); if ((bits & protocol.SU_WEAPONFRAME) != 0) { msg.WriteByte((int)ent.v.weaponframe); } if ((bits & protocol.SU_ARMOR) != 0) { msg.WriteByte((int)ent.v.armorvalue); } if ((bits & protocol.SU_WEAPON) != 0) { msg.WriteByte(ModelIndex(progs.GetString(ent.v.weaponmodel))); } msg.WriteShort((int)ent.v.health); msg.WriteByte((int)ent.v.currentammo); msg.WriteByte((int)ent.v.ammo_shells); msg.WriteByte((int)ent.v.ammo_nails); msg.WriteByte((int)ent.v.ammo_rockets); msg.WriteByte((int)ent.v.ammo_cells); if (common.GameKind == GameKind.StandardQuake) { msg.WriteByte((int)ent.v.weapon); } else { for (int i = 0; i < 32; i++) { if ((((int)ent.v.weapon) & (1 << i)) != 0) { msg.WriteByte(i); break; } } } }
/// <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_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... /// </summary> private static int TryUnstick(edict_t ent, ref v3f oldvel) { v3f oldorg = ent.v.origin; v3f dir = Common.ZeroVector3f; trace_t steptrace = new trace_t(); for (int i = 0; i < 8; i++) { // try pushing a little in an axial direction switch (i) { case 0: dir.x = 2; dir.y = 0; break; case 1: dir.x = 0; dir.y = 2; break; case 2: dir.x = -2; dir.y = 0; break; case 3: dir.x = 0; dir.y = -2; break; case 4: dir.x = 2; dir.y = 2; break; case 5: dir.x = -2; dir.y = 2; break; case 6: dir.x = 2; dir.y = -2; break; case 7: dir.x = -2; dir.y = -2; break; } PushEntity(ent, ref dir); // retry the original move ent.v.velocity.x = oldvel.x; ent.v.velocity.y = oldvel.y; ent.v.velocity.z = 0; int clip = FlyMove(ent, 0.1f, steptrace); if (Math.Abs(oldorg.y - ent.v.origin.y) > 4 || Math.Abs(oldorg.x - ent.v.origin.x) > 4) { return(clip); } // go back to the original pos and try again ent.v.origin = oldorg; } ent.v.velocity = Common.ZeroVector3f; return(7); // still not moving }
/// <summary> /// SV_WalkMove /// Only used by players /// </summary> static void WalkMove(edict_t ent) { // // do a regular slide move unless it looks like you ran into a step // int oldonground = (int)ent.v.flags & EdictFlags.FL_ONGROUND; ent.v.flags = (int)ent.v.flags & ~EdictFlags.FL_ONGROUND; v3f oldorg = ent.v.origin; v3f oldvel = ent.v.velocity; trace_t steptrace = new trace_t(); int clip = FlyMove(ent, (float)Host.FrameTime, steptrace); if ((clip & 2) == 0) { return; // move didn't block on a step } if (oldonground == 0 && ent.v.waterlevel == 0) { return; // don't stair up while jumping } if (ent.v.movetype != Movetypes.MOVETYPE_WALK) { return; // gibbed by a trigger } if (_NoStep.Value != 0) { return; } if (((int)_Player.v.flags & EdictFlags.FL_WATERJUMP) != 0) { return; } v3f nosteporg = ent.v.origin; v3f nostepvel = ent.v.velocity; // // try moving up and forward to go up a step // ent.v.origin = oldorg; // back to start pos v3f upmove = Common.ZeroVector3f; v3f downmove = upmove; upmove.z = STEPSIZE; downmove.z = (float)(-STEPSIZE + oldvel.z * Host.FrameTime); // move up PushEntity(ent, ref upmove); // FIXME: don't link? // move forward ent.v.velocity.x = oldvel.x; ent.v.velocity.y = oldvel.y; ent.v.velocity.z = 0; clip = FlyMove(ent, (float)Host.FrameTime, steptrace); // check for stuckness, possibly due to the limited precision of floats // in the clipping hulls if (clip != 0) { if (Math.Abs(oldorg.y - ent.v.origin.y) < 0.03125 && Math.Abs(oldorg.x - ent.v.origin.x) < 0.03125) { // stepping up didn't make any progress clip = TryUnstick(ent, ref oldvel); } } // extra friction based on view angle if ((clip & 2) != 0) { WallFriction(ent, steptrace); } // move down trace_t downtrace = PushEntity(ent, ref downmove); // FIXME: don't link? if (downtrace.plane.normal.Z > 0.7) { if (ent.v.solid == Solids.SOLID_BSP) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(downtrace.ent); } } else { // 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 ent.v.origin = nosteporg; ent.v.velocity = nostepvel; } }
/// <summary> /// SV_Physics_Toss /// Toss, bounce, and fly movement. When onground, do nothing. /// </summary> private static void Physics_Toss(edict_t ent) { // regular thinking if (!RunThink(ent)) { return; } // if onground, return without moving if (((int)ent.v.flags & EdictFlags.FL_ONGROUND) != 0) { return; } CheckVelocity(ent); // add gravity if (ent.v.movetype != Movetypes.MOVETYPE_FLY && ent.v.movetype != Movetypes.MOVETYPE_FLYMISSILE) { AddGravity(ent); } // move angles Mathlib.VectorMA(ref ent.v.angles, (float)Host.FrameTime, ref ent.v.avelocity, out ent.v.angles); // move origin v3f move; Mathlib.VectorScale(ref ent.v.velocity, (float)Host.FrameTime, out move); trace_t trace = PushEntity(ent, ref move); if (trace.fraction == 1) { return; } if (ent.free) { return; } float backoff; if (ent.v.movetype == Movetypes.MOVETYPE_BOUNCE) { backoff = 1.5f; } else { backoff = 1; } ClipVelocity(ref ent.v.velocity, ref trace.plane.normal, out ent.v.velocity, backoff); // stop if on ground if (trace.plane.normal.Z > 0.7f) { if (ent.v.velocity.z < 60 || ent.v.movetype != Movetypes.MOVETYPE_BOUNCE) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(trace.ent); ent.v.velocity = default(v3f); ent.v.avelocity = default(v3f); } } // check for in water CheckWaterTransition(ent); }
/// <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; }
static void SetMinMaxSize(edict_t e, ref Vector3 min, ref Vector3 max, bool rotate) { if (min.X > max.X || min.Y > max.Y || min.Z > max.Z) { Progs.RunError("backwards mins/maxs"); } rotate = false; // FIXME: implement rotation properly again Vector3 rmin = min, rmax = max; if (!rotate) { //rmin = min; //rmax = max; } else { // find min / max for rotations //angles = e.v.angles; //a = angles[1] / 180 * M_PI; //xvector[0] = cos(a); //xvector[1] = sin(a); //yvector[0] = -sin(a); //yvector[1] = cos(a); //VectorCopy(min, bounds[0]); //VectorCopy(max, bounds[1]); //rmin[0] = rmin[1] = rmin[2] = 9999; //rmax[0] = rmax[1] = rmax[2] = -9999; //for (i = 0; i <= 1; i++) //{ // base[0] = bounds[i][0]; // for (j = 0; j <= 1; j++) // { // base[1] = bounds[j][1]; // for (k = 0; k <= 1; k++) // { // base[2] = bounds[k][2]; // // transform the point // transformed[0] = xvector[0] * base[0] + yvector[0] * base[1]; // transformed[1] = xvector[1] * base[0] + yvector[1] * base[1]; // transformed[2] = base[2]; // for (l = 0; l < 3; l++) // { // if (transformed[l] < rmin[l]) // rmin[l] = transformed[l]; // if (transformed[l] > rmax[l]) // rmax[l] = transformed[l]; // } // } // } //} } // set derived values Mathlib.Copy(ref rmin, out e.v.mins); Mathlib.Copy(ref rmax, out e.v.maxs); Vector3 s = max - min; Mathlib.Copy(ref s, out e.v.size); Server.LinkEdict(e, false); }
/// <summary> /// SV_Physics_Noclip /// A moving object that doesn't obey physics /// </summary> static void Physics_Noclip(edict_t ent) { // regular thinking if (!RunThink(ent)) return; Mathlib.VectorMA(ref ent.v.angles, (float)Host.FrameTime, ref ent.v.avelocity, out ent.v.angles); Mathlib.VectorMA(ref ent.v.origin, (float)Host.FrameTime, ref ent.v.velocity, out ent.v.origin); LinkEdict(ent, false); }
/// <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_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 /// If steptrace is not NULL, the trace of any vertical wall hit will be stored /// </summary> static int FlyMove(edict_t ent, float time, trace_t steptrace) { v3f original_velocity = ent.v.velocity; v3f primal_velocity = ent.v.velocity; int numbumps = 4; int blocked = 0; Vector3[] planes = new Vector3[MAX_CLIP_PLANES]; int numplanes = 0; float time_left = time; for (int bumpcount = 0; bumpcount < numbumps; bumpcount++) { if (ent.v.velocity.IsEmpty) { break; } v3f end; Mathlib.VectorMA(ref ent.v.origin, time_left, ref ent.v.velocity, out end); trace_t trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref end, 0, ent); if (trace.allsolid) { // entity is trapped in another solid ent.v.velocity = default(v3f); return(3); } if (trace.fraction > 0) { // actually covered some distance Mathlib.Copy(ref trace.endpos, out ent.v.origin); original_velocity = ent.v.velocity; numplanes = 0; } if (trace.fraction == 1) { break; // moved the entire distance } if (trace.ent == null) { Sys.Error("SV_FlyMove: !trace.ent"); } if (trace.plane.normal.Z > 0.7) { blocked |= 1; // floor if (trace.ent.v.solid == Solids.SOLID_BSP) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(trace.ent); } } if (trace.plane.normal.Z == 0) { blocked |= 2; // step if (steptrace != null) { steptrace.CopyFrom(trace); // save for player extrafriction } } // // run the impact function // Impact(ent, trace.ent); if (ent.free) { break; // removed by the impact function } time_left -= time_left * trace.fraction; // cliped to another plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen ent.v.velocity = default(v3f); return(3); } planes[numplanes] = trace.plane.normal; numplanes++; // // modify original_velocity so it parallels all of the clip planes // v3f new_velocity = default(v3f); int i, j; for (i = 0; i < numplanes; i++) { ClipVelocity(ref original_velocity, ref planes[i], out new_velocity, 1); for (j = 0; j < numplanes; j++) { if (j != i) { float dot = new_velocity.x * planes[j].X + new_velocity.y * planes[j].Y + new_velocity.z * planes[j].Z; if (dot < 0) { break; // not ok } } } if (j == numplanes) { break; } } if (i != numplanes) { // go along this plane ent.v.velocity = new_velocity; } else { // go along the crease if (numplanes != 2) { ent.v.velocity = default(v3f); return(7); } Vector3 dir = Vector3.Cross(planes[0], planes[1]); float d = dir.X * ent.v.velocity.x + dir.Y * ent.v.velocity.y + dir.Z * ent.v.velocity.z; Mathlib.Copy(ref dir, out ent.v.velocity); Mathlib.VectorScale(ref ent.v.velocity, d, out ent.v.velocity); } // // if original velocity is against the original velocity, stop dead // to avoid tiny occilations in sloping corners // if (Mathlib.DotProduct(ref ent.v.velocity, ref primal_velocity) <= 0) { ent.v.velocity = default(v3f); return(blocked); } } return(blocked); }
public void Clear() { this.active = false; this.spawned = false; this.dropasap = false; this.privileged = false; this.sendsignon = false; this.last_message = 0; this.netconnection = null; this.cmd.Clear(); this.wishdir = Vector3.Zero; this.message.Clear(); this.edict = null; this.name = null; this.colors = 0; Array.Clear(this.ping_times, 0, this.ping_times.Length); this.num_pings = 0; Array.Clear(this.spawn_parms, 0, this.spawn_parms.Length); this.old_frags = 0; }
/// <summary> /// 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 /// If steptrace is not NULL, the trace of any vertical wall hit will be stored /// </summary> static int FlyMove(edict_t ent, float time, trace_t steptrace) { v3f original_velocity = ent.v.velocity; v3f primal_velocity = ent.v.velocity; int numbumps = 4; int blocked = 0; Vector3[] planes = new Vector3[MAX_CLIP_PLANES]; int numplanes = 0; float time_left = time; for (int bumpcount = 0; bumpcount < numbumps; bumpcount++) { if (ent.v.velocity.IsEmpty) break; v3f end; Mathlib.VectorMA(ref ent.v.origin, time_left, ref ent.v.velocity, out end); trace_t trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref end, 0, ent); if (trace.allsolid) { // entity is trapped in another solid ent.v.velocity = default(v3f); return 3; } if (trace.fraction > 0) { // actually covered some distance Mathlib.Copy(ref trace.endpos, out ent.v.origin); original_velocity = ent.v.velocity; numplanes = 0; } if (trace.fraction == 1) break; // moved the entire distance if (trace.ent == null) Sys.Error("SV_FlyMove: !trace.ent"); if (trace.plane.normal.Z > 0.7) { blocked |= 1; // floor if (trace.ent.v.solid == Solids.SOLID_BSP) { ent.v.flags = (int)ent.v.flags | EdictFlags.FL_ONGROUND; ent.v.groundentity = EdictToProg(trace.ent); } } if (trace.plane.normal.Z == 0) { blocked |= 2; // step if (steptrace != null) steptrace.CopyFrom(trace); // save for player extrafriction } // // run the impact function // Impact(ent, trace.ent); if (ent.free) break; // removed by the impact function time_left -= time_left * trace.fraction; // cliped to another plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen ent.v.velocity = default(v3f); return 3; } planes[numplanes] = trace.plane.normal; numplanes++; // // modify original_velocity so it parallels all of the clip planes // v3f new_velocity = default(v3f); int i, j; for (i = 0; i < numplanes; i++) { ClipVelocity(ref original_velocity, ref planes[i], out new_velocity, 1); for (j = 0; j < numplanes; j++) if (j != i) { float dot = new_velocity.x * planes[j].X + new_velocity.y * planes[j].Y + new_velocity.z * planes[j].Z; if (dot < 0) break; // not ok } if (j == numplanes) break; } if (i != numplanes) { // go along this plane ent.v.velocity = new_velocity; } else { // go along the crease if (numplanes != 2) { ent.v.velocity = default(v3f); return 7; } Vector3 dir = Vector3.Cross(planes[0], planes[1]); float d = dir.X * ent.v.velocity.x + dir.Y * ent.v.velocity.y + dir.Z * ent.v.velocity.z; Mathlib.Copy(ref dir, out ent.v.velocity); Mathlib.VectorScale(ref ent.v.velocity, d, out ent.v.velocity); } // // if original velocity is against the original velocity, stop dead // to avoid tiny occilations in sloping corners // if (Mathlib.DotProduct(ref ent.v.velocity, ref primal_velocity) <= 0) { ent.v.velocity = default(v3f); return blocked; } } return blocked; }
static trace_t Move(ref v3f start, ref v3f mins, ref v3f maxs, ref v3f end, int type, edict_t passedict) { Vector3 vstart, vmins, vmaxs, vend; Mathlib.Copy(ref start, out vstart); Mathlib.Copy(ref mins, out vmins); Mathlib.Copy(ref maxs, out vmaxs); Mathlib.Copy(ref end, out vend); return(Move(ref vstart, ref vmins, ref vmaxs, ref vend, type, passedict)); }
/// <summary> /// SV_Move /// mins and maxs are relative /// if the entire move stays in a solid volume, trace.allsolid will be set /// if the starting point is in a solid, it will be allowed to move out to an open area /// nomonsters is used for line of sight or edge testing, where mosnters /// shouldn't be considered solid objects /// passedict is explicitly excluded from clipping checks (normally NULL) /// </summary> public static trace_t Move(ref Vector3 start, ref Vector3 mins, ref Vector3 maxs, ref Vector3 end, int type, edict_t passedict) { moveclip_t clip = new moveclip_t(); // clip to world clip.trace = ClipMoveToEntity(sv.edicts[0], ref start, ref mins, ref maxs, ref end); clip.start = start; clip.end = end; clip.mins = mins; clip.maxs = maxs; clip.type = type; clip.passedict = passedict; if (type == MOVE_MISSILE) { clip.mins2 = Vector3.One * -15; clip.maxs2 = Vector3.One * 15; } else { clip.mins2 = mins; clip.maxs2 = maxs; } // create the bounding box of the entire move MoveBounds(ref start, ref clip.mins2, ref clip.maxs2, ref end, out clip.boxmins, out clip.boxmaxs); // clip to entities ClipToLinks(_AreaNodes[0], clip); return clip.trace; }
/// <summary> /// EDICT_TO_PROG(e) /// </summary> public static int EdictToProg(edict_t e) { return Array.IndexOf(_Server.edicts, e); // todo: optimize this }
/// <summary> /// SV_UnlinkEdict /// call before removing an entity, and before trying to move one, /// so it doesn't clip against itself /// flags ent->v.modified /// </summary> public static void UnlinkEdict(edict_t ent) { if (ent.area.Prev == null) return; // not linked in anywhere ent.area.Remove(); //RemoveLink(&ent->area); //ent->area.prev = ent->area.next = NULL; }
/// <summary> /// NUM_FOR_EDICT /// </summary> public static int NumForEdict(edict_t e) { int i = Array.IndexOf(sv.edicts, e); // todo: optimize this if (i < 0) Sys.Error("NUM_FOR_EDICT: bad pointer"); return i; }
/// <summary> /// SV_ClipMoveToEntity /// Handles selection or creation of a clipping hull, and offseting (and /// eventually rotation) of the end points /// </summary> static trace_t ClipMoveToEntity(edict_t ent, ref Vector3 start, ref Vector3 mins, ref Vector3 maxs, ref Vector3 end) { trace_t trace = new trace_t(); // fill in a default trace trace.fraction = 1; trace.allsolid = true; trace.endpos = end; // get the clipping hull Vector3 offset; hull_t hull = HullForEntity(ent, ref mins, ref maxs, out offset); Vector3 start_l = start - offset; Vector3 end_l = end - offset; // trace a line through the apropriate clipping hull RecursiveHullCheck(hull, hull.firstclipnode, 0, 1, ref start_l, ref end_l, trace); // fix trace up by the offset if (trace.fraction != 1) trace.endpos += offset; // did we clip the move? if (trace.fraction < 1 || trace.startsolid) trace.ent = ent; return trace; }