Single player only runs on its server thread now. --in-frame and
--no-server-thread print a notice, and HORIZON_SERVER_THREAD is ignored.
This deletes the code that only the in-frame path reached: the window's
world loader and local player tick, its portal travel, the loading screen
and ring-by-ring streaming, and the in-frame LAN host (the host player in
the hub, its models of the guests, the reserved uuid). The world-upgrade
progress screen with its Cancel button moves to the server-thread start.
Tools, the harnesses and the docs no longer use the in-frame path.
Closes#6473
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Match vanilla 26.3 ClientPacketListener.handleGameEvent by playing the arrow hit player sound on event 6 at eye height, the pufferfish sting sound on event 9 at entity position, and updating do_limited_crafting on event 12.
Co-Authored-By: Antigravity <[email protected]>
A command the player didn't type went straight to the server. Now
verifyCommand decides: a parse error or a restricted command asks before
it goes, and one with a message argument only reaches the chat box, or
the clipboard over a screen, for the player to send. Refs #6080.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Dispatch HealthEvent::DamageBroadcast and MotionEvent::HurtSound for guests in leave_guest and guest_server_tick (along with record_combat and wear_armor in leave_guest) so every unblocked player hurt immediately sends ClientboundDamageEventPacket with its exact DamageTypeId and broadcasts the hurt sound to nearby players.
- Run tick_guests and the deferred guest attack/effect flushes before lan_flush_effects and lan_swings in tick_lan so captured sounds and mob damage events from guest turns are flushed on the same tick and in the right dimension.
- Attack live mobs directly via attack_mob in guest_attack after within_attack_range instead of turning the guest's view to the mob's center with look_at_entity and re-running raycast pick checks.
- Handle ClientboundSoundEntityPacket in remote.rs and prefer inline RegistryData effects in hurt_sound_for.
Fixes#4330
Co-Authored-By: Antigravity (Gemini) <[email protected]>
ClientPacketListener.handlePlayerChat checks the global chat index (a mismatch
disconnects), the sender's PlayerInfo, and the sender's SignedMessageValidator:
keyed by the chat session the services' profile-key keys vouch for, or the
fallback of enforcesSecureChat. A message that fails shows the red italic
chat.validation_error in its chat type, tagged chatError
(ChatListener.handleChatMessageError); one a validator takes without a
signature shows as unsigned. The services' keys are fetched once per
connection off the game thread.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ClientPacketListener.handleProjectilePowerPacket finds the entity by id and,
when it is a hurting projectile, takes the power it accelerates by. Horizon
decoded the packet and dropped it, so a deflected fireball - whose power
halves - kept the spawn default on the client between tracker updates. Our
own server already sends it bundled with the motion.
The client uses #4214's SignatureCache and its handling of a bad packed id, instead of its
own cache. markMessageAsProcessed runs where the chat takes a message, past any Chat Delay,
with whether it was shown; handleDeleteChat now calls LastSeenTracker::ignore_pending.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Online servers with enforce-secure-profile=true (26.3's default) refused every chat
message and /msg from Horizon ("Chat disabled due to missing profile public key").
game/remote_chat.rs (ClientPacketListener):
- setKeyPair: after each login packet, and when the key pair is renewed, a chat session
(random id, chain from link 0) goes to the server as ServerboundChatSessionUpdatePacket,
only when the server plays the account under its own profile id: an offline-mode
server's offline id gets none, so chat stays unsigned there as before.
- sendChat / sendCommand: chat and each message argument of a command (SignableCommand.of
over the server's tree) are signed with the same time, salt and last seen, one chain
link each; every packet carries the last-seen update.
- handlePlayerChat / markMessageAsProcessed: other players' packed last seen through the
signature cache, and a ChatAck once more than 64 messages wait.
- sendUnattendedCommand: a clicked chat line, a dialog's button and a mod's command stay
unsigned (vanilla asks before signing one; there is no such screen here).
tools/secure_chat_client_e2e.py: vanilla 26.3's server and a mock of the services that
issues certificates; online with enforce-secure-profile (chat logged as secure, a /msg
whisper delivered, nothing refused) and offline (unsigned, as before). The net harness
takes its chat lines from HORIZON_NET_CHAT and reports whether it signed.
Fixes the LocalChatSession.createMessageEncoder, SignedMessageChain.encoder,
ClientPacketListener.setKeyPair and ClientPacketListener.sendCommand rows of
horizon-rust/horizon#3520 and horizon-rust/horizon#3518.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a connection (a server, a LAN world, and single player, whose world
runs on its own server thread) F3+I copied the client's partial copy of
the block or mob and called it server-side: a chest lost its items, a mob
its tags and brain. Vanilla's KeyboardHandler.copyRecreateCommand asks the
server through DebugQueryHandler (ServerboundBlockEntityTagQueryPacket,
ServerboundEntityTagQueryPacket) and copies the ClientboundTagQueryPacket
answer to the newest query.
Add the three packets, the query state on the connection and its answer,
and the server side for guests (ServerGamePacketListenerImpl
.handleBlockEntityTagQuery and handleEntityTagQuery, for game masters
only). Part of #3520 (its handleBlockEntityTagQuery and
handleEntityTagQuery rows).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientboundCustomChatCompletionsPacket was only an id constant, so the
names and tags proxies and plugins add with it (players on other backend
servers, chat tags) never showed when Tab completes chat. Decode it, keep
the connection's set like ClientSuggestionProvider.modifyCustomCompletions
(ADD, REMOVE, SET; an unknown action id is ADD), clear it on a
reconfiguration, and complete chat from getCustomTabSuggestions: the
players and the custom completions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientboundDeleteChatPacket (31) was only a constant: Horizon never decoded
it, and the PlayerChat handler dropped the signature a deletion names a
message by, so a line a moderation plugin deleted stayed on screen.
Decode the packet (MessageSignature.Packed), keep the connection's
MessageSignatureCache (unpack the last seen of each PlayerChat, then push it
and the signature, as ClientPacketListener.handlePlayerChat does), and keep
each player message's signature in the chat. handleDeleteChat: a message
still held back by Chat Delay never shows (removeFromDelayedMessageQueue);
a shown one becomes the grey italic chat.deleted_marker line once it has
shown 60 ticks (ChatComponent.deleteMessageOrDelay, with the delayed queue
ChatComponent.tick processes). An unknown packed id disconnects with
multiplayer.disconnect.invalid_packet, as vanilla does.
Fixes#3518
Co-Authored-By: Claude Opus 5.5 <[email protected]>