the unit frames are the reference's own, and the water has motes in it
- the player, party, target and pet frames are the reference's own files (`BuffFrame`, `UnitFrame`, `CombatFeedback`, `PlayerFrame`, `PartyFrame`, `TargetFrame`, `PetFrame`, in the reference's toc order) — our 2741-line `UnitFrames.xml` and our `PartyFrame.xml` are deleted; four engine verbs built for them (`PartialPlayTime`, `NoPlayTime`, `UnitPlayerControlled`, `UnitIsCivilian`, each answering 1/nil), the whole-UI load bracketed in the counted sound-suppression scope `0x48fbf0` self-brackets (the reference fires a `PlaySound` during load and throws it away), and `GetBillingTimeRested` read off `SMSG_AUTH_RESPONSE`'s third billing field rather than hardcoded; the differences the stock files bring are pinned rather than papered — `PlayerFrame` never hides, an unresolved name is BLANK not "Unknown", the target frame has no numerals at all (a bar hover pops the unit tooltip instead) - what the swap surfaced, each a hard break under the stock files: `UnitBuff`/`UnitDebuff` returned the Classic Era ten-value tuple, name first, where 1.12 returns two and three values TEXTURE first (`0x519500`/`0x5198f0`) — a raise twice per `UNIT_AURA` on any debuffed target, and all 184 corpus call sites bind the 1.12 shape; the power events are named per resource (`UNIT_MANA`/`RAGE`/`FOCUS`/`ENERGY`/`HAPPINESS` and their MAX twins) and we fired the Era pair, so no mana, rage or energy bar could live-update; `SetRaidTarget` is the engine verb and `SetRaidTargetIcon` the FrameXML toggle wrapper — we had registered the wrapper's name on the engine's body, so every raid mark in the unit popup raised - the underwater drift cloud: the reference draws a fixed 4000-mote field whenever the camera eye is inside a liquid — not a particle system, nothing spawns, ages or dies; a population scattered once into a 30 yd camera-relative cube, advected by the camera's own motion plus a gust and wrapped, so the field stands still in the world; benilla had the murk and nothing in it; a dry frame commits no draw, and the submerged one is bounded by the reference's own 666-quad cap (633–644 of 4000 measured live, which is the expected survivor count of a uniform cube through the 90° cone — an independent check on population, distribution and cone at once) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -195,7 +195,15 @@ pub enum SessionEvent {
|
||||
CharActionResult { action: CharAction, code: u8 },
|
||||
/// We are in the world: `self_guid` is our player, `name` its character name. The IO thread emits
|
||||
/// this first, before any object updates.
|
||||
Connected { self_guid: u64, name: String },
|
||||
Connected {
|
||||
self_guid: u64,
|
||||
name: String,
|
||||
/// The account's accumulated **rested billing minutes**, from the `SMSG_AUTH_RESPONSE`
|
||||
/// that admitted this session — what `GetBillingTimeRested()` reports (decision 1820).
|
||||
/// Rides the connect event because that is the only moment it ever arrives: the reference
|
||||
/// keeps it in a process-lifetime global written once by the auth parser.
|
||||
billing_time_rested: u32,
|
||||
},
|
||||
/// The server confirmed our logout (`SMSG_LOGOUT_COMPLETE`) — we are back at character select.
|
||||
/// The IO thread cycles the connection immediately; a fresh [`Self::CharacterList`] follows.
|
||||
LoggedOut,
|
||||
|
||||
@@ -85,6 +85,15 @@ pub enum ServerPacket {
|
||||
/// Our place in the login queue, when `result` is [`super::AUTH_WAIT_QUEUE`] — `None` for
|
||||
/// every other result, and also for a queue packet whose body was too short to carry one.
|
||||
queue_position: Option<u32>,
|
||||
/// The account's accumulated **rested billing minutes**, the third field of the billing
|
||||
/// group — what `GetBillingTimeRested()` reports (decision 1820). `None` when the body was
|
||||
/// too short to carry the group, which is the same length branch `queue_position` takes.
|
||||
///
|
||||
/// The unit is the server's convention, not the engine's: the client applies no conversion
|
||||
/// at any point (the binding `0x48ec50` does a bare unsigned u32→double, and the parser
|
||||
/// `0x418eb0` a bare `mov`). Minutes is what the reference's own FrameXML assumes —
|
||||
/// `PlayerFrame.lua:246` divides by 60 to get hours against `REQUIRED_REST_HOURS = 5`.
|
||||
billing_time_rested: Option<u32>,
|
||||
},
|
||||
CharEnum {
|
||||
characters: Vec<Character>,
|
||||
|
||||
@@ -191,14 +191,21 @@ pub fn parse_server(opcode: u16, body: &[u8]) -> io::Result<ServerPacket> {
|
||||
// position as current is a bug we decline to reproduce.
|
||||
let result = read_u8(&mut r)?;
|
||||
let queued = result == super::AUTH_WAIT_QUEUE;
|
||||
let mut billing_time_rested = None;
|
||||
if (result == super::AUTH_OK || queued) && r.len() >= 5 {
|
||||
let _billing_time_remaining = read_u32_le(&mut r)?;
|
||||
let _billing_plan_flags = read_u8(&mut r)?;
|
||||
let _billing_time_rested = read_u32_le(&mut r)?;
|
||||
// Kept rather than dropped since 1820: this is the only place the value ever
|
||||
// arrives, and `GetBillingTimeRested()` is a plain read of it. The client parks it
|
||||
// in a process-lifetime global (`[ClientServices+0x1af8]`, sole writer `0x5b421a`);
|
||||
// ours rides the session, which has the same lifetime and cannot go stale across a
|
||||
// reconnect the way that global does.
|
||||
billing_time_rested = Some(read_u32_le(&mut r)?);
|
||||
}
|
||||
ServerPacket::AuthResponse {
|
||||
result,
|
||||
queue_position: queued.then(|| read_u32_le(&mut r).ok()).flatten(),
|
||||
billing_time_rested,
|
||||
}
|
||||
}
|
||||
opcode::SMSG_CHAR_ENUM => {
|
||||
|
||||
@@ -90,6 +90,10 @@ pub struct WorldSession {
|
||||
/// vmangos drops any chat whose language the character doesn't know (dot-commands included) —
|
||||
/// hardcoded Common silently ate every Horde character's chat and commands once.
|
||||
chat_language: u32,
|
||||
/// The rested billing minutes the admitting `SMSG_AUTH_RESPONSE` carried, or `0` when its body
|
||||
/// was too short for the billing group. Read by [`Self::billing_time_rested`]; see decision
|
||||
/// 1820 for why `0` rather than the client's own uninitialised-global behaviour.
|
||||
billing_time_rested: u32,
|
||||
}
|
||||
|
||||
impl WorldSession {
|
||||
@@ -178,6 +182,7 @@ impl WorldSession {
|
||||
crypto,
|
||||
roster_races: Default::default(),
|
||||
chat_language: messages::LANGUAGE_COMMON,
|
||||
billing_time_rested: 0,
|
||||
};
|
||||
|
||||
// 4. Wait for SMSG_AUTH_RESPONSE. Usually the first encrypted packet, but not always first
|
||||
@@ -191,7 +196,17 @@ impl WorldSession {
|
||||
// honest message at the login screen.
|
||||
loop {
|
||||
match session.recv()? {
|
||||
ServerPacket::AuthResponse { result, .. } if result == messages::AUTH_OK => break,
|
||||
ServerPacket::AuthResponse {
|
||||
result,
|
||||
billing_time_rested,
|
||||
..
|
||||
} if result == messages::AUTH_OK => {
|
||||
// The admitting packet is the only one that counts: a queue packet carries the
|
||||
// group too, but the client re-reads it on every AUTH_RESPONSE and the last one
|
||||
// wins, which is this one.
|
||||
session.billing_time_rested = billing_time_rested.unwrap_or(0);
|
||||
break;
|
||||
}
|
||||
// **Queued, not refused** — the realm is full and we keep our place in line. Report
|
||||
// the position and go back to reading; the server re-sends as we move up and ends
|
||||
// the wait with an `AUTH_OK`. Treating this as an ending (which it was until now)
|
||||
@@ -199,6 +214,7 @@ impl WorldSession {
|
||||
ServerPacket::AuthResponse {
|
||||
result,
|
||||
queue_position,
|
||||
..
|
||||
} if result == messages::AUTH_WAIT_QUEUE => {
|
||||
if !queued {
|
||||
queued = true;
|
||||
@@ -230,6 +246,17 @@ impl WorldSession {
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
/// The account's accumulated **rested billing minutes**, from the `SMSG_AUTH_RESPONSE` that
|
||||
/// admitted this session — what `GetBillingTimeRested()` reports (decision 1820).
|
||||
///
|
||||
/// Against vmangos this is always `0`: the server hardcodes `uint32(0)` for the field
|
||||
/// (`World.cpp:331`, and `:380` for the queue packet), guarded on a build newer than 1.7.1,
|
||||
/// which 1.12 is. It is read from the wire rather than assumed so that a server which does
|
||||
/// populate it is reported honestly.
|
||||
pub fn billing_time_rested(&self) -> u32 {
|
||||
self.billing_time_rested
|
||||
}
|
||||
|
||||
/// Set a read timeout on the underlying socket (e.g. so a debug read-loop can stop when the
|
||||
/// world goes quiet). `None` clears it (blocking reads).
|
||||
pub fn set_read_timeout(&self, timeout: Option<std::time::Duration>) -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user