|
From: <xer...@us...> - 2026-07-30 15:26:52
|
Revision: 1687
http://sourceforge.net/p/seq/svn/1687
Author: xerxes00
Date: 2026-07-30 15:26:50 +0000 (Thu, 30 Jul 2026)
Log Message:
-----------
Legends: remove dead self-state path, buffChange structs, cleanup
- drop 38-byte eqlClientSelfPosStruct + len==38 branch (dead on 0729)
- buffChange decodes via buffChangeStruct/buffChangeEQLStruct (live 16B@8, Legends 24B@12)
Modified Paths:
--------------
showeq/branches/showeqlegends/src/everquest.h
showeq/branches/showeqlegends/src/packetformat.h
showeq/branches/showeqlegends/src/packetstream.cpp
showeq/branches/showeqlegends/src/player.cpp
showeq/branches/showeqlegends/src/s_everquest.h
showeq/branches/showeqlegends/src/spellshell.cpp
Modified: showeq/branches/showeqlegends/src/everquest.h
===================================================================
--- showeq/branches/showeqlegends/src/everquest.h 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/everquest.h 2026-07-30 15:26:50 UTC (rev 1687)
@@ -1161,8 +1161,8 @@
struct
{
- // EQ Legends 2026-07-29 posData: low 19 bits of three
- // consecutive words, field >> 3. Field names keep the EQL
+ // posData: low 19 bits of three consecutive words,
+ // field >> 3. Field names keep the EQL
// transposed convention consumed by setPos in spawn.cpp.
// High 13 bits presumed deltas (unvalidated); words 3-4
// unmapped and zeroed at consumption.
@@ -1591,7 +1591,7 @@
struct playerPosUpdateEQLStruct
{
/*0000*/ uint32_t spawnId;
-/*0004*/ signed z:19; // >>3 - EQL 0729 layout
+/*0004*/ signed z:19; // >>3
signed deltaZ:13;
/*0008*/ signed x:19; // >>3
signed deltaX:13;
@@ -2612,32 +2612,8 @@
/*0042*/
};
-// EQ Legends 2026-07-28 real-time client self-state (the 38-byte body on
-// the c2s OP_ClientUpdate/OP_SelfPosEQL stream). Wire-verified against
-// /loc: y@6, z@10, heading@26 (facing packed in the low 21 bits), x@34.
-struct eqlClientSelfPosStruct
-{
-/*0000*/ uint16_t ctr; // update counter
-/*0002*/ uint16_t spawnId; // player's spawn id
-/*0004*/ uint16_t unknown0004;
-/*0006*/ float y; // y coord (loc value 2)
-/*0010*/ float z; // z coord (loc value 3)
-/*0014*/ uint8_t unknown0014[8];
-/*0022*/ union {
- uint32_t headingRaw; // raw dword @22
- struct {
- unsigned :20; // low 20 bits unused for facing
- unsigned heading :11; // facing 0..2047 (bits 20-30); swept a full circle on a spin
- unsigned :1;
- };
- };
-/*0026*/ uint8_t unknown0026[8];
-/*0034*/ float x; // x coord (loc value 1)
-/*0038*/
-};
-
-// EQ Legends 2026-07-29 client self-state on the c2s OP_ClientUpdate
-// 42-byte record - freshest self position + facing (EQ h2048).
+// Client self-state on the c2s OP_ClientUpdate 42-byte record -
+// freshest self position + facing (EQ h2048).
// A 44-byte variant exists; trailing bytes are tolerated.
struct eqlClientUpdateSelfStruct
{
@@ -2872,10 +2848,36 @@
/*0154*/ uint8_t unknown0106[6];
/*0160*/ uint32_t spellslot; // buff slot in buff window
/*0164*/ uint32_t changetype; // 1=buff fading,2=buff duration
-/*0168*/
+/*0168*/
};
/*
+** buffChangeStruct - single buff apply/fade (OP_Buff, server direction).
+** One handler decodes both servers; changeType sits at a different offset
+** per server, so each has its own struct, selected by record length:
+** buffChangeStruct - live, 16-byte record, changeType @8
+** buffChangeEQLStruct - Legends, 24-byte record, changeType @12
+** changeType: 1 = faded/clicked off, 4 = applied.
+*/
+
+struct buffChangeStruct
+{
+/*0000*/ uint32_t buffSlot; // buff-window slot (0-14)
+/*0004*/ uint32_t spellId; // spell id
+/*0008*/ uint32_t changeType; // 1 = fade, 4 = apply
+/*0012*/
+};
+
+struct buffChangeEQLStruct
+{
+/*0000*/ uint32_t buffSlot; // buff-window slot (0-14)
+/*0004*/ uint32_t spellId; // spell id
+/*0008*/ uint32_t unknown0008;
+/*0012*/ uint32_t changeType; // 1 = fade, 4 = apply
+/*0016*/
+};
+
+/*
** Guild Member Update structure
** Length: 88 Octets
**
Modified: showeq/branches/showeqlegends/src/packetformat.h
===================================================================
--- showeq/branches/showeqlegends/src/packetformat.h 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/packetformat.h 2026-07-30 15:26:50 UTC (rev 1687)
@@ -161,19 +161,18 @@
// Bare packets come in two wire forms on EQ Legends, matching
// live's model. Flag-split packets ([op_lo][flag][op_hi]
// [payload][crc16], flag 0xa5 raw or 0x5a zlib) use the
- // opcode-split format - the 0x0b03 OP_ClientUpdate family
- // (client self-state and server spawn waypoints), 0x2824, and
- // OP_NpcMoveUpdate (relocated to 0x2824 on Legends,
- // live-identical bit format, wire-verified: spawnIds in its
- // records match live zone NPCs) travel this way. Plain packets (net op LE
- // uint16, payload at byte 2, no flags, no CRC - the 0x71fc
+ // opcode-split format - the OP_ClientUpdate family (client
+ // self-state and server spawn waypoints) and OP_NpcMoveUpdate
+ // (live-identical bit format, wire-verified: spawnIds in its
+ // records match live zone NPCs) travel this way. Plain packets
+ // (net op LE uint16, payload at byte 2, no flags, no CRC - the
// keepalive family) do not. Split only when byte 1 is a real
// protocol flag. Restricted to the 0x03 low-byte family:
// every legitimate flag-split channel observed has low byte
// 0x03, and an unrestricted byte-1 test false-fires on plain
- // opcodes whose HIGH byte is 0xa5 or 0x5a (OP_NewZone 0x5ab6
- // is one), mangling them into random opcodes with shifted
- // payloads - the source class of the PC-racing regression.
+ // opcodes whose HIGH byte is 0xa5 or 0x5a (OP_NewZone is one),
+ // mangling them into random opcodes with shifted payloads -
+ // the source class of the PC-racing regression.
return m_length >= 3 &&
eqFlagSplitLowByte[m_packet[0]] &&
(m_packet[1] == PROTOCOL_FLAG_COMPRESSED ||
Modified: showeq/branches/showeqlegends/src/packetstream.cpp
===================================================================
--- showeq/branches/showeqlegends/src/packetstream.cpp 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/packetstream.cpp 2026-07-30 15:26:50 UTC (rev 1687)
@@ -628,17 +628,6 @@
packet.getNetOpCode() == 0x0b03)
return;
- // EQ Legends: the bare flag-split form of the spawn waypoint opcode
- // (0x0b03, server direction) is a majority-undecoded record layout
- // that misreads as position garbage. Only the OP_Packet-wrapped form
- // is dispatched - the exact milestone-f population, which field
- // testing confirmed acceptable. Client direction (self-state) uses a
- // separate stream and is unaffected. Remove when the record layout
- // is wire-solved.
- if (m_dir == DIR_Server && packet.hasFlags() && !packet.isSubpacket() &&
- packet.getNetOpCode() == 0x0b03)
- return;
-
processPacket(packet, false); // false = isn't subpacket
// if the cache isn't empty, then process it.
Modified: showeq/branches/showeqlegends/src/player.cpp
===================================================================
--- showeq/branches/showeqlegends/src/player.cpp 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/player.cpp 2026-07-30 15:26:50 UTC (rev 1687)
@@ -945,169 +945,11 @@
if (dir == DIR_Client)
{
- // Movement experiment: keepalive position, glide deltas, and
- // travel-direction heading are disabled (2026-07-08: glide was
- // re-tried against the 0x0201 record variants and rejected in
- // game - live does not glide the self dot either). Self position
- // comes from the last-record scan in playerSelfState; facing from
- // the 42B self-state variant.
+ // Client-direction self-updates are a no-op here: self position
+ // comes from the last-record scan in playerSelfState, and facing
+ // from the self-state variant. This handler serves only the
+ // server-direction path below.
return;
-
- if (len < 12)
- return;
-
- // Legends client upload: 17-byte records tiled from offset 0.
- // float x@0, float y@4, float z@8, flag u8@12, msec counter u16@13,
- // sequence u16@15. Big packets (~1/sec while moving) carry a full
- // second of sub-second samples; the tail window of those samples
- // supplies velocity and travel heading. The client transmits no
- // facing while stationary, so heading holds its last value at rest.
- bool found = false;
- float fx = 0.0f, fy = 0.0f, fz = 0.0f;
- uint16_t lastCtr = 0;
- for (size_t off = 0; off + 17 <= len; off += 17)
- {
- const eqlSelfPosRecord* r = (const eqlSelfPosRecord*)(data + off);
- float tx = r->y, ty = r->x, tz = r->z;
- if (tx > -100000.0f && tx < 100000.0f &&
- ty > -100000.0f && ty < 100000.0f &&
- tz > -10000.0f && tz < 10000.0f &&
- (tx != 0.0f || ty != 0.0f))
- {
- fx = tx; fy = ty; fz = tz; found = true;
- lastCtr = r->counter;
- }
- }
- if (!found)
- return;
-
- // Velocity window: the sane record closest to 250ms before the last
- // record, located via the per-record msec counters (u16 wrap safe).
- // A tail window tracks the current direction of travel instead of
- // averaging across the whole second, which cuts corners on turns.
- bool haveBack = false;
- float bx = 0.0f, by = 0.0f, bz = 0.0f;
- uint16_t backCtr = 0;
- for (size_t off = 0; off + 17 <= len; off += 17)
- {
- const eqlSelfPosRecord* r = (const eqlSelfPosRecord*)(data + off);
- float tx = r->y, ty = r->x, tz = r->z;
- if (!(tx > -100000.0f && tx < 100000.0f &&
- ty > -100000.0f && ty < 100000.0f &&
- tz > -10000.0f && tz < 10000.0f &&
- (tx != 0.0f || ty != 0.0f)))
- continue;
- uint16_t tctr = r->counter;
- uint16_t age = (uint16_t)(lastCtr - tctr);
- if (age >= 250 && age < 5000)
- {
- bx = tx; by = ty; bz = tz; backCtr = tctr; haveBack = true;
- }
- }
-
- // Freshness arbitration: this packet's newest record is about
- // one second stale on arrival, so while the self-state stream
- // (playerSelfState) is delivering positions, applying it would
- // snap the dot backwards by up to a second of travel. When
- // self-state goes quiet (some movement modes emit it at
- // multi-second gaps) this becomes the freshest source and takes
- // over as the 1Hz fallback.
- bool selfStateFresh = m_selfStateAge.isValid() &&
- m_selfStateAge.elapsed() < 2500;
- if (!selfStateFresh)
- {
- int16_t px = int16_t(fy);
- int16_t py = int16_t(fx);
- int16_t pz = int16_t(fz);
- setPos(px, py, pz, showeq_params->walkpathrecord, showeq_params->walkpathlength);
- }
-
- // Map convention transpose (map x = wire y) applies to velocity too
- float vxm = 0.0f, vym = 0.0f, vzm = 0.0f;
- if (haveBack)
- {
- float dts = ((uint16_t)(lastCtr - backCtr)) / 1000.0f;
- if (dts > 0.05f)
- {
- vxm = (fy - by) / dts;
- vym = (fx - bx) / dts;
- vzm = (fz - bz) / dts;
- }
- }
-
- // Stop detection: a short window over the last ~100ms of records.
- // If end-of-packet speed collapsed relative to the tail window, the
- // player stopped as the packet closed - zero the glide immediately
- // instead of overshooting for up to a second and snapping back.
- bool haveShort = false;
- float sx = 0.0f, sy = 0.0f;
- uint16_t shortCtr = 0;
- for (size_t off = 0; off + 17 <= len; off += 17)
- {
- const eqlSelfPosRecord* r = (const eqlSelfPosRecord*)(data + off);
- float tx = r->y, ty = r->x, tz = r->z;
- if (!(tx > -100000.0f && tx < 100000.0f &&
- ty > -100000.0f && ty < 100000.0f &&
- tz > -10000.0f && tz < 10000.0f &&
- (tx != 0.0f || ty != 0.0f)))
- continue;
- uint16_t tctr = r->counter;
- uint16_t age = (uint16_t)(lastCtr - tctr);
- if (age >= 80 && age < 250)
- {
- sx = tx; sy = ty; shortCtr = tctr; haveShort = true;
- }
- }
- float shortSpd = -1.0f;
- if (haveShort)
- {
- float sdts = ((uint16_t)(lastCtr - shortCtr)) / 1000.0f;
- if (sdts > 0.02f)
- shortSpd = hypotf((fy - sy) / sdts, (fx - sx) / sdts);
- }
- bool stopping = (shortSpd >= 0.0f) &&
- (shortSpd < 0.35f * hypotf(vxm, vym));
-
- if (hypotf(vxm, vym) > 1.0f)
- {
- // setDeltas cooks delta * 0.0013 per msec, so delta = v / 1.3
- // glides the dot at true speed under AnimateSpawnMovement.
- // A stopping packet still updates heading but does not glide.
- if (stopping)
- setDeltas(0, 0, 0);
- else
- setDeltas(int16_t(lrintf(vxm / 1.3f)),
- int16_t(lrintf(vym / 1.3f)),
- int16_t(lrintf(vzm / 1.3f)));
- // Travel direction converted to EQ heading units (0-2047, CCW
- // from north), the same space live's self-update carries, so the
- // live compass/reticle formulas below apply unchanged
- // Travel-direction heading is the fallback only: the
- // self-state stream carries true facing at a higher rate, so
- // heading is derived from velocity only when that stream is
- // quiet.
- if (!selfStateFresh)
- {
- float eqdeg = atan2f(vxm, vym) * (180.0f / (float)M_PI);
- if (eqdeg < 0.0f)
- eqdeg += 360.0f;
- int h2048 = ((int)lrintf(eqdeg * 2048.0f / 360.0f)) & 0x7ff;
- setHeading(h2048, 0);
- m_headingDegrees = 360 - ((h2048 * 360) >> 11);
- }
- }
- else
- {
- setDeltas(0, 0, 0);
- }
-
- m_validPos = true;
- updateLast();
- emit headingChanged(m_headingDegrees);
- emit posChanged(x(), y(), z(), deltaX(), deltaY(), deltaZ(), m_headingDegrees);
- updateLastChanged();
- emit changeItem(this, tSpawnChangedPosition);
- return;
}
if ((dir != DIR_Client) && (pupdate->spawnId != id()))
@@ -1271,51 +1113,8 @@
if (dir != DIR_Client || len < 16)
return;
- // EQ Legends 2026-07-15: the real-time CLIENT self-state - a 38-byte
- // float body carrying the player's own spawnId @2, the freshest
- // position, AND the only client-side facing.
- // 2026-07-28 patch: the 0728 client rotated the field offsets within
- // the same 38-byte body (wire-verified from delivered bytes - @6/@34
- // tracked movement smoothly, @14 became a ~0 velocity delta, @18 went
- // to a constant 0, and @26 now carries the quiet-NaN facing):
- // float Z @10, float mapX @6, float mapY @34,
- // u32 heading @26 (facing in the low 21 bits; full circle 2^21, so
- // (&0x1FFFFF)>>10 = EQ h2048).
- // Old 0714 layout was mapX@14, heading@18, mapY@26.
- // This supersedes the 1.5-2s-stale %17 flush below for freshness
- // (m_selfStateAge gate). If the dot lands mirrored, swap @6/@34.
- if (len == 38)
- {
- const eqlClientSelfPosStruct *sp = (const eqlClientSelfPosStruct*)data;
- float zz = sp->z, mx = sp->x, my = sp->y;
- if ((mx != 0.0f || my != 0.0f) &&
- fabsf(mx) < 100000.0f && fabsf(my) < 100000.0f && fabsf(zz) < 10000.0f)
- {
- m_selfStateAge.start();
- setPos(int16_t(mx), int16_t(my), int16_t(zz),
- showeq_params->walkpathrecord, showeq_params->walkpathlength);
- setDeltas(0, 0, 0);
- m_validPos = true;
- updateLast();
- emit posChanged(x(), y(), z(), deltaX(), deltaY(), deltaZ(),
- m_headingDegrees);
- updateLastChanged();
- emit changeItem(this, tSpawnChangedPosition);
- }
- int h2048 = sp->heading;
- if (h2048 <= 2047)
- {
- setHeading(h2048, 0);
- m_headingDegrees = 360 - ((h2048 * 360) >> 11);
- emit headingChanged(m_headingDegrees);
- updateLastChanged();
- emit changeItem(this, tSpawnChangedPosition);
- }
- return;
- }
-
- // EQ Legends 2026-07-08: the float-leading variants (len % 17 == 1:
- // 18/35/52/69/86 and the 1700-3400B moving-state flushes) tile
+ // Float-leading variants (len % 17 == 1: 18/35/52/69/86 and the
+ // 1700-3400B moving-state flushes) tile
// 17-byte records from offset 0 (float map-Y, float map-X, float Z,
// flag u8, u16 ms counter, u16 seq). The LAST sane record is the
// freshest sample; reading only the first record was the source of
@@ -1326,7 +1125,6 @@
if (len % 17 == 1)
{
float wx = 0, wy = 0, wz = 0;
- uint16_t lastCtr = 0;
bool plausible = false;
for (size_t off = 0; off + 17 <= len; off += 17)
{
@@ -1340,7 +1138,6 @@
if (ok)
{
wy = ty; wx = tx; wz = tz;
- lastCtr = r->counter;
plausible = true;
}
}
@@ -1352,9 +1149,8 @@
m_selfStateAge.elapsed() < 2500;
if (plausible && !selfStateFresh)
{
- // Deltas stay zero by explicit decision (2026-07-08, second
- // rejection in game): dead-reckoned glide overshoots on stops
- // and does not match how Tom runs live. Pure stepping on the
+ // Deltas stay zero: dead-reckoned glide overshoots on stops and
+ // does not match live client behavior. Pure stepping on the
// freshest record.
// standing EQ Legends transposition: float0 is map Y, float1 is
// map X; assign accordingly.
Modified: showeq/branches/showeqlegends/src/s_everquest.h
===================================================================
--- showeq/branches/showeqlegends/src/s_everquest.h 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/s_everquest.h 2026-07-30 15:26:50 UTC (rev 1687)
@@ -111,7 +111,6 @@
AddStruct(randomStruct);
AddStruct(playerSpawnPosStruct);
AddStruct(playerSelfPosStruct);
-AddStruct(eqlClientSelfPosStruct);
AddStruct(eqlClientUpdateSelfStruct);
AddStruct(eqlSelfPosRecord);
AddStruct(spawnAppearanceStruct);
@@ -131,6 +130,8 @@
AddStruct(serverLFGStruct);
AddStruct(clientLFGStruct);
AddStruct(buffStruct);
+AddStruct(buffChangeStruct);
+AddStruct(buffChangeEQLStruct);
AddStruct(GuildMemberUpdate);
AddStruct(bazaarTraderRequest);
AddStruct(bazaarSearchQueryStruct);
Modified: showeq/branches/showeqlegends/src/spellshell.cpp
===================================================================
--- showeq/branches/showeqlegends/src/spellshell.cpp 2026-07-30 02:10:06 UTC (rev 1686)
+++ showeq/branches/showeqlegends/src/spellshell.cpp 2026-07-30 15:26:50 UTC (rev 1687)
@@ -352,10 +352,20 @@
if (dir == DIR_Client || len < 16)
return;
- uint32_t buffSlot = *(const uint32_t*)data;
- uint32_t spellId = *(const uint32_t*)(data + 4);
- uint32_t changeType = (len >= 24) ? *(const uint32_t*)(data + 12)
- : *(const uint32_t*)(data + 8);
+ // Same handler serves both servers; changeType offset differs, so
+ // select the per-server struct by record length (layout lives in
+ // buffChangeStruct / buffChangeEQLStruct).
+ uint32_t buffSlot, spellId, changeType;
+ if (len >= 24) // Legends 24-byte record, changeType @12
+ {
+ const buffChangeEQLStruct* b = (const buffChangeEQLStruct*)data;
+ buffSlot = b->buffSlot; spellId = b->spellId; changeType = b->changeType;
+ }
+ else // live 16-byte record, changeType @8
+ {
+ const buffChangeStruct* b = (const buffChangeStruct*)data;
+ buffSlot = b->buffSlot; spellId = b->spellId; changeType = b->changeType;
+ }
// Real buff-window slots are 0-14; higher slots are scribes/bar refreshes.
if (buffSlot >= 15)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|