Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions DistrictServer/ApbUdp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,44 @@ namespace
maximum);
}

// FRotator::SerializeCompressed (UnMath.cpp:84), the format the
// client's UActorChannel property reader expects for Rotator fields.
// Per component: 1 bit "high byte nonzero?" then, if nonzero, the
// 8-bit high byte (value >> 8). This is NOT the width-field format
// FVector uses; the previous implementation mirrored
// WriteCompressedVector and the client misparsed every rotation
// bunch (the remote pawn never rotated). Verified round-trip in
// rAPB/Emulator/DistrictServer/test_u3_primitives.cpp (yaw 16384
// reconstructs to 16384; a pure-yaw rotation writes only the yaw
// byte: pitch/roll each write a single 0 bit).
void WriteCompressedRotator(
std::int32_t pitch,
std::int32_t yaw,
std::int32_t roll)
{
const std::uint8_t bytePitch =
static_cast<std::uint8_t>(
(static_cast<std::uint32_t>(pitch) & 0xFFFFu) >> 8);
const std::uint8_t byteYaw =
static_cast<std::uint8_t>(
(static_cast<std::uint32_t>(yaw) & 0xFFFFu) >> 8);
const std::uint8_t byteRoll =
static_cast<std::uint8_t>(
(static_cast<std::uint32_t>(roll) & 0xFFFFu) >> 8);

WriteBit(bytePitch != 0);
if (bytePitch != 0)
WriteBits(bytePitch, 8);

WriteBit(byteYaw != 0);
if (byteYaw != 0)
WriteBits(byteYaw, 8);

WriteBit(byteRoll != 0);
if (byteRoll != 0)
WriteBits(byteRoll, 8);
}

// FString as UE3 serialises it: a 32 bit length (including the
// terminator) followed by the characters and a trailing null. Same
// encoding the text control messages already use.
Expand Down Expand Up @@ -1257,6 +1295,69 @@ namespace ApbUdp
return writer.FinishWithTrailer();
}

// High-frequency replicated property updates (remote-pawn position/
// rotation/velocity) use UNRELIABLE bunches: each update self-corrects on
// the next tick, and reliable bunches at ~30 Hz per property overflow the
// client's reliable channel and wedge the actor channel (the remote pawn
// silently disappears). Header mirrors the ClientAckGoodMove path:
// PacketId, IsAck=0, no open/close flags, Reliable=0, ChannelIndex,
// DataBits, payload. No channel sequence for unreliable bunches.
std::vector<std::uint8_t> BuildUnreliableActorVectorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
float x,
float y,
float z)
{
BitWriter payload;
payload.WriteBoundedInt(fieldIndex, fieldMax);
payload.WriteCompressedVector(x, y, z);

BitWriter writer;
writer.WriteBits(serverPacketId & 0x3FFFFFFFu, 30);
writer.WriteBit(false);
writer.WriteBit(false);
writer.WriteBit(false);
writer.WriteBoundedInt(channelIndex, 0x3FFu);
writer.WriteBoundedInt(
static_cast<std::uint32_t>(payload.BitCount()),
512u * 8u);
writer.WriteBitsFrom(
payload.Snapshot(),
payload.BitCount());
return writer.FinishWithTrailer();
}

std::vector<std::uint8_t> BuildUnreliableActorRotatorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
std::int32_t pitch,
std::int32_t yaw,
std::int32_t roll)
{
BitWriter payload;
payload.WriteBoundedInt(fieldIndex, fieldMax);
payload.WriteCompressedRotator(pitch, yaw, roll);

BitWriter writer;
writer.WriteBits(serverPacketId & 0x3FFFFFFFu, 30);
writer.WriteBit(false);
writer.WriteBit(false);
writer.WriteBit(false);
writer.WriteBoundedInt(channelIndex, 0x3FFu);
writer.WriteBoundedInt(
static_cast<std::uint32_t>(payload.BitCount()),
512u * 8u);
writer.WriteBitsFrom(
payload.Snapshot(),
payload.BitCount());
return writer.FinishWithTrailer();
}

// One field carrying a list of 32 bit integers, e.g.
// Receive_DS2GC_ANS_DISTRICT_ENTER(returnCode, districtUID, instanceNo).
std::vector<std::uint8_t> BuildActorIntFieldPacket(
Expand Down Expand Up @@ -1464,6 +1565,28 @@ namespace ApbUdp
return writer.FinishWithTrailer();
}

std::vector<std::uint8_t> BuildActorRotatorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint16_t channelSequence,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
std::int32_t pitch,
std::int32_t yaw,
std::int32_t roll)
{
BitWriter payload;
payload.WriteBoundedInt(fieldIndex, fieldMax);
payload.WriteCompressedRotator(pitch, yaw, roll);

BitWriter writer;
WriteActorBunchHeader(
writer, serverPacketId, channelIndex, channelSequence,
payload.BitCount());
writer.WriteBitsFrom(payload.Snapshot(), payload.BitCount());
return writer.FinishWithTrailer();
}

// ClientUpdateLevelStreamingStatus(name PackageName, bool bShouldBeLoaded,
// bool bShouldBeVisible, bool bBlockOnLoad)
// USES only registers a package with the package map; this is what makes
Expand Down
34 changes: 34 additions & 0 deletions DistrictServer/ApbUdp.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,40 @@ namespace ApbUdp
float z,
bool compressed);

// FRotator property update (Pitch/Yaw/Roll in UE3 rotation units,
// 65536 per full turn), serialized with the compressed-rotator format
// (FRotator::SerializeCompressed family).
std::vector<std::uint8_t> BuildActorRotatorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint16_t channelSequence,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
std::int32_t pitch,
std::int32_t yaw,
std::int32_t roll);

// Unreliable variants for high-frequency property updates (remote-pawn
// position/rotation/velocity). No channel sequence; the client does not
// ACK them and missed updates self-correct on the next tick.
std::vector<std::uint8_t> BuildUnreliableActorVectorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
float x,
float y,
float z);

std::vector<std::uint8_t> BuildUnreliableActorRotatorFieldPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
std::uint32_t fieldIndex,
std::uint32_t fieldMax,
std::int32_t pitch,
std::int32_t yaw,
std::int32_t roll);

std::vector<std::uint8_t> BuildLevelStreamingStatusPacket(
std::uint32_t serverPacketId,
std::uint16_t channelIndex,
Expand Down
Loading