/// <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_ClientThink /// the move fields specify an intended velocity in pix/sec /// the angle fields specify an exact angular motion in degrees /// </summary> static void ClientThink() { if (_Player.v.movetype == Movetypes.MOVETYPE_NONE) { return; } _OnGround = ((int)_Player.v.flags & EdictFlags.FL_ONGROUND) != 0; DropPunchAngle(); // // if dead, behave differently // if (_Player.v.health <= 0) { return; } // // angles // show 1/3 the pitch angle and all the roll angle _Cmd = Host.HostClient.cmd; v3f v_angle; Mathlib.VectorAdd(ref _Player.v.v_angle, ref _Player.v.punchangle, out v_angle); Vector3 pang = Common.ToVector(ref _Player.v.angles); Vector3 pvel = Common.ToVector(ref _Player.v.velocity); _Player.v.angles.z = View.CalcRoll(ref pang, ref pvel) * 4; if (_Player.v.fixangle == 0) { _Player.v.angles.x = -v_angle.x / 3; _Player.v.angles.y = v_angle.y; } if (((int)_Player.v.flags & EdictFlags.FL_WATERJUMP) != 0) { WaterJump(); return; } // // walk // if ((_Player.v.waterlevel >= 2) && (_Player.v.movetype != Movetypes.MOVETYPE_NOCLIP)) { WaterMove(); return; } AirMove(); }
/// <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_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]); } }
/* * ============= * 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> /// SV_movestep /// Called by monster program code. /// The move will be adjusted for slopes and stairs, but if the move isn't /// possible, no move is done, false is returned, and /// pr_global_struct.trace_normal is set to the normal of the blocking wall /// </summary> public static bool MoveStep(edict_t ent, ref v3f move, bool relink) { trace_t trace; // try the move v3f oldorg = ent.v.origin; v3f neworg; Mathlib.VectorAdd(ref ent.v.origin, ref move, out neworg); // flying monsters don't step up if (((int)ent.v.flags & (EdictFlags.FL_SWIM | EdictFlags.FL_FLY)) != 0) { // try one move with vertical motion, then one without for (int i = 0; i < 2; i++) { Mathlib.VectorAdd(ref ent.v.origin, ref move, out neworg); edict_t enemy = ProgToEdict(ent.v.enemy); if (i == 0 && enemy != sv.edicts[0]) { float dz = ent.v.origin.z - enemy.v.origin.z; if (dz > 40) { neworg.z -= 8; } if (dz < 30) { neworg.z += 8; } } trace = Move(ref ent.v.origin, ref ent.v.mins, ref ent.v.maxs, ref neworg, 0, ent); if (trace.fraction == 1) { if (((int)ent.v.flags & EdictFlags.FL_SWIM) != 0 && PointContents(ref trace.endpos) == Contents.CONTENTS_EMPTY) { return(false); // swim monster left water } Mathlib.Copy(ref trace.endpos, out ent.v.origin); if (relink) { LinkEdict(ent, true); } return(true); } if (enemy == sv.edicts[0]) { break; } } return(false); } // push down from a step height above the wished position neworg.z += STEPSIZE; v3f end = neworg; end.z -= STEPSIZE * 2; trace = Move(ref neworg, ref ent.v.mins, ref ent.v.maxs, ref end, 0, ent); if (trace.allsolid) { return(false); } if (trace.startsolid) { neworg.z -= STEPSIZE; trace = Move(ref neworg, ref ent.v.mins, ref ent.v.maxs, ref end, 0, ent); if (trace.allsolid || trace.startsolid) { return(false); } } if (trace.fraction == 1) { // if monster had the ground pulled out, go ahead and fall if (((int)ent.v.flags & EdictFlags.FL_PARTIALGROUND) != 0) { Mathlib.VectorAdd(ref ent.v.origin, ref move, out ent.v.origin); if (relink) { LinkEdict(ent, true); } ent.v.flags = (int)ent.v.flags & ~EdictFlags.FL_ONGROUND; return(true); } return(false); // walked off an edge } // check point traces down for dangling corners Mathlib.Copy(ref trace.endpos, out ent.v.origin); if (!CheckBottom(ent)) { if (((int)ent.v.flags & EdictFlags.FL_PARTIALGROUND) != 0) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) { LinkEdict(ent, true); } return(true); } ent.v.origin = oldorg; return(false); } if (((int)ent.v.flags & EdictFlags.FL_PARTIALGROUND) != 0) { ent.v.flags = (int)ent.v.flags & ~EdictFlags.FL_PARTIALGROUND; } ent.v.groundentity = EdictToProg(trace.ent); // the move is ok if (relink) { LinkEdict(ent, true); } return(true); }
/// <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); }
/// <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 != Sound.DEFAULT_SOUND_PACKET_VOLUME) { field_mask |= Protocol.SND_VOLUME; } if (attenuation != Sound.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); }