public void ChangeDemoDir(string fileName) { const string newDir = "sussy baka uwu"; var before = GetDemo(fileName); MemoryStream outStream = new MemoryStream(); OptChangeDemoDir.ChangeDemoDir(before, outStream, newDir); SourceDemo after = new SourceDemo(outStream.ToArray()); after.Parse(); // check that all the packets are unchanged CollectionAssert.AreEqual( before.Frames.Select(frame => frame.Type), after.Frames.Select(frame => frame.Type)); // check that all messages are unchanged CollectionAssert.AreEqual( before.FilterForMessages().Select(t => t.messageType), after.FilterForMessages().Select(t => t.messageType)); // check that the new demo has the new dir in header and SvcServerInfo messages Assert.AreEqual(after.Header.GameDirectory, newDir); Assert.That(after.FilterForMessage <SvcServerInfo>().Select(t => t.message.GameDir), Is.All.EqualTo(newDir)); }
/* * Okay so this is long and wacky and honestly I don't remember exactly how this works because I threw it * together for rendering glitchless v2 a while ago. Sooooooooo, the general idea is to smoothly transition the * player's origin and view offset during a jump. * * * Normally, a jump will look something like this (each column is a tick): * * cam pos: 👁️👁️👁️👁️👁️ 👁️👁️👁️👁️👁️ * 👁️👁️👁️👁️ 👁️👁️👁️👁️ * 👁️👁️👁️ 👁️👁️👁️ * 👁️👁️ * * origin pos: 🦶🦶🦶🦶🦶 🦶🦶🦶🦶🦶 * 🦶🦶🦶🦶 🦶🦶🦶🦶 * 🦶🦶🦶 🦶🦶🦶 * * * * 🦶🦶 * floor: ------------------------------------------------------------ * * God bless you if you don't see emojis here. Notice that the player is in a crouched state until they get * close to the floor. For the 2 (in this case) ticks that the player is on the floor, they are standing. To * compensate for this, the view offset is increased and therein lies the problem. It seems like the player's * view offset transitions smoothly regardless of demo_interpolateview and always happens a tick too late or * something like that. This makes jumps extremely ugly without demo interp and slightly bouncy with interp. * The goal of this function is to "bridge the gap" if you will, and pull the player's feet up during a jump. * We find jumps by looking at "m_Local.m_flJumpTime", and then linearly interpolate the player's origin and * view offset across the ticks when they are on the ground. There's some spicy stuff below, this is probably * something that should be improved in the future. */ public static void SmoothJumps(SourceDemo demo, Stream s, int maxGroundTicks) { // ReSharper disable once CompareOfFloatsByEqualityOperator var jumpTicks = (from msgTup in demo.FilterForMessage <SvcPacketEntities>() from update in msgTup.message.Updates where update.ServerClass.ClassName == "CPortal_Player" && update is Delta from deltaProp in ((Delta)update).Props where deltaProp.prop.Name == "m_Local.m_flJumpTime" && ((SingleEntProp <float>)deltaProp.prop).Value == 510.0 select msgTup.tick).ToList(); Console.WriteLine($"found jump ticks: {jumpTicks.SequenceToString()}"); BitStreamWriter bsw = new BitStreamWriter(demo.Reader.Data); const float interpThreshold = 15; // this many units off the ground is considered a jump var frames = demo.Frames; foreach (int jumpTick in jumpTicks) { int jumpTickIdx = 0; for (; frames[jumpTickIdx].Type != PacketType.Packet; jumpTickIdx++) { ; // find first packet } while (frames[++jumpTickIdx].Tick < jumpTick) { ; // find the first frame with the matching tick } for (; frames[jumpTickIdx].Type != PacketType.Packet; jumpTickIdx++) { ; // find the packet on this tick } Packet groundPacket = (Packet)frames[jumpTickIdx].Packet !; Vector3 groundPos = groundPacket.PacketInfo[0].ViewOrigin; // roughly the ground pos int endTick; Vector3 endVec; int idx = jumpTickIdx; while (frames[++idx].Type != PacketType.Packet) { ; // jump to next packet } Vector3 curViewOrigin = ((Packet)frames[idx].Packet !).PacketInfo[0].ViewOrigin; if (curViewOrigin.Z - groundPos.Z > interpThreshold) { endTick = ((Packet)frames[idx].Packet !).Tick; endVec = curViewOrigin; } else { Console.WriteLine($"not patching jump on tick {jumpTick}, too many ticks before air time detected"); continue; } Vector3 startVec = new Vector3(float.PositiveInfinity); idx = jumpTickIdx; bool shouldInterp = true; for (int ticksBeforeJump = 0; ticksBeforeJump < maxGroundTicks; ticksBeforeJump++) { while (frames[--idx].Type != PacketType.Packet) { ; // jump to previous packet } Packet curPacket = (Packet)frames[idx].Packet !; curViewOrigin = curPacket.PacketInfo[0].ViewOrigin; if (curViewOrigin.Z - groundPos.Z > interpThreshold) { startVec = curViewOrigin; break; } if (ticksBeforeJump == maxGroundTicks - 1) { shouldInterp = false; Console.WriteLine($"not patching jump on tick {jumpTick}, too many ticks on ground (at least {maxGroundTicks}) before this jump"); break; } } if (!shouldInterp) { continue; } int startTick = frames[idx].Tick; // idx is currently the start tick (right before the first interp tick) while (frames[idx].Tick < endTick) { while (frames[++idx].Type != PacketType.Packet) { ; // jump to next packet } BitStreamWriter vecBytes = new BitStreamWriter(); Packet packet = (Packet)frames[idx].Packet !; float lerpAmount = (float)(frames[idx].Tick - startTick) / (endTick - startTick); Vector3 lerpVec = Vector3.Lerp(startVec, endVec, lerpAmount); vecBytes.WriteVector3(lerpVec); // edit the pos in the cmdinfo, we don't actually need to edit the origin in the ent data since that isn't used during demo playback bsw.EditBitsAtIndex(vecBytes, packet.Reader.AbsoluteStart + 32); var viewOffsetProp = (SingleEntProp <float>?)( (Delta?)packet.FilterForMessage <SvcPacketEntities>().Single().Updates ! .SingleOrDefault(update => update.ServerClass.ClassName == "CPortal_Player")) ?.Props.SingleOrDefault(tuple => tuple.prop.Name == "m_vecViewOffset[2]").prop; if (viewOffsetProp != null) { BitStreamWriter viewOffsetBytes = new BitStreamWriter(); viewOffsetBytes.WriteFloat(28); bsw.EditBitsAtIndex(viewOffsetBytes, viewOffsetProp.Offset); } } } s.Write(bsw, 0, bsw.ByteLength); }