Broadcast teleport landing sound to nearby users
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Maintainer-controlled web client version.
|
||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R237";
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R238";
|
||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||
|
||||
@@ -1095,6 +1095,7 @@ function updateTeleport(): void {
|
||||
state.player.x = activeTeleport.targetX;
|
||||
state.player.y = activeTeleport.targetY;
|
||||
signaling.send({ type: 'update_position', x: activeTeleport.targetX, y: activeTeleport.targetY });
|
||||
signaling.send({ type: 'teleport_complete' });
|
||||
activeTeleport = null;
|
||||
stopTeleportLoopAudio();
|
||||
persistPlayerPosition();
|
||||
|
||||
@@ -158,6 +158,13 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
||||
break;
|
||||
}
|
||||
|
||||
case 'teleport_complete': {
|
||||
if (deps.getAudioLayers().world) {
|
||||
deps.playRemoteSpatialStepOrTeleport(deps.TELEPORT_SOUND_URL, message.x, message.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'update_nickname': {
|
||||
const peer = deps.state.peers.get(message.id);
|
||||
if (peer) {
|
||||
|
||||
@@ -103,6 +103,13 @@ export const updatePositionSchema = z.object({
|
||||
y: z.number().int(),
|
||||
});
|
||||
|
||||
export const teleportCompleteSchema = z.object({
|
||||
type: z.literal('teleport_complete'),
|
||||
id: z.string(),
|
||||
x: z.number().int(),
|
||||
y: z.number().int(),
|
||||
});
|
||||
|
||||
export const updateNicknameSchema = z.object({
|
||||
type: z.literal('update_nickname'),
|
||||
id: z.string(),
|
||||
@@ -199,6 +206,7 @@ export const incomingMessageSchema = z.discriminatedUnion('type', [
|
||||
welcomeMessageSchema,
|
||||
signalMessageSchema,
|
||||
updatePositionSchema,
|
||||
teleportCompleteSchema,
|
||||
updateNicknameSchema,
|
||||
userLeftSchema,
|
||||
chatMessageSchema,
|
||||
@@ -217,6 +225,7 @@ export type IncomingMessage = z.infer<typeof incomingMessageSchema>;
|
||||
export type OutgoingMessage =
|
||||
| { type: 'signal'; targetId: string; sdp?: RTCSessionDescriptionInit; ice?: RTCIceCandidateInit }
|
||||
| { type: 'update_position'; x: number; y: number }
|
||||
| { type: 'teleport_complete' }
|
||||
| { type: 'update_nickname'; nickname: string }
|
||||
| { type: 'chat_message'; message: string }
|
||||
| { type: 'ping'; clientSentAt: number }
|
||||
|
||||
@@ -11,6 +11,7 @@ This is a behavior guide for packet semantics beyond raw schemas.
|
||||
## Client -> Server
|
||||
|
||||
- `update_position`: client movement intent; server enforces world bounds and movement rate policy.
|
||||
- `teleport_complete`: client signals teleport landing; server rebroadcasts spatial landing cue.
|
||||
- `update_nickname`: nickname change request (server enforces uniqueness).
|
||||
- `chat_message`: player chat.
|
||||
- `ping`: latency measurement.
|
||||
@@ -23,6 +24,7 @@ This is a behavior guide for packet semantics beyond raw schemas.
|
||||
- `welcome`: initial snapshot with users/items plus server UI/world metadata.
|
||||
- `signal`: forwarded WebRTC offer/answer/ICE.
|
||||
- `update_position`, `update_nickname`, `user_left`: presence updates.
|
||||
- `teleport_complete`: peer teleport landing event with spatial coordinates.
|
||||
- `chat_message`: system and user chat stream.
|
||||
- `pong`: ping response.
|
||||
- `nickname_result`: accepted/rejected nickname result.
|
||||
@@ -41,6 +43,7 @@ This is a behavior guide for packet semantics beyond raw schemas.
|
||||
- `item_piano_status` carries machine-readable piano events (`use_mode_entered`, record/playback transitions).
|
||||
- `item_use_sound` contains absolute item world coordinates (`x`, `y`) and sound path.
|
||||
- For carried items, source coordinates resolve to the carrier's current position.
|
||||
- `teleport_complete` contains absolute player world coordinates (`x`, `y`) at teleport landing.
|
||||
- `item_piano_note` contains:
|
||||
- `itemId`, `senderId`, `keyId`, `midi`, `on`
|
||||
- resolved `instrument`, `voiceMode`, `octave`, `attack`, `decay`, `release`, `brightness`, `emitRange`
|
||||
|
||||
@@ -39,6 +39,7 @@ Core incoming message effects:
|
||||
|
||||
- `signal`: WebRTC negotiation and ICE exchange.
|
||||
- `update_position`: update peer position; may play movement/teleport world sound.
|
||||
- `teleport_complete`: play peer teleport landing sound at final tile.
|
||||
- `update_nickname`: update peer display name.
|
||||
- `chat_message`: append/readable status; optional system sound class.
|
||||
- `item_upsert`: replace item snapshot and resync item runtimes.
|
||||
|
||||
@@ -25,6 +25,10 @@ class UpdatePositionPacket(BasePacket):
|
||||
y: int
|
||||
|
||||
|
||||
class TeleportCompletePacket(BasePacket):
|
||||
type: Literal["teleport_complete"]
|
||||
|
||||
|
||||
class UpdateNicknamePacket(BasePacket):
|
||||
type: Literal["update_nickname"]
|
||||
nickname: str = Field(min_length=1, max_length=32)
|
||||
@@ -91,6 +95,7 @@ class ItemUpdatePacket(BasePacket):
|
||||
ClientPacket = (
|
||||
SignalPacket
|
||||
| UpdatePositionPacket
|
||||
| TeleportCompletePacket
|
||||
| UpdateNicknamePacket
|
||||
| ChatMessagePacket
|
||||
| PingPacket
|
||||
@@ -135,6 +140,13 @@ class BroadcastPositionPacket(BasePacket):
|
||||
y: int
|
||||
|
||||
|
||||
class BroadcastTeleportCompletePacket(BasePacket):
|
||||
type: Literal["teleport_complete"]
|
||||
id: str
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
class BroadcastNicknamePacket(BasePacket):
|
||||
type: Literal["update_nickname"]
|
||||
id: str
|
||||
|
||||
@@ -42,6 +42,7 @@ from .models import (
|
||||
BroadcastChatMessagePacket,
|
||||
BroadcastNicknamePacket,
|
||||
BroadcastPositionPacket,
|
||||
BroadcastTeleportCompletePacket,
|
||||
ChatMessagePacket,
|
||||
ClientPacket,
|
||||
ForwardSignalPacket,
|
||||
@@ -63,6 +64,7 @@ from .models import (
|
||||
PingPacket,
|
||||
PongPacket,
|
||||
RemoteUser,
|
||||
TeleportCompletePacket,
|
||||
UpdateNicknamePacket,
|
||||
UpdatePositionPacket,
|
||||
UserLeftPacket,
|
||||
@@ -879,6 +881,18 @@ class SignalingServer:
|
||||
await self._broadcast_item(carried)
|
||||
return
|
||||
|
||||
if isinstance(packet, TeleportCompletePacket):
|
||||
await self._broadcast(
|
||||
BroadcastTeleportCompletePacket(
|
||||
type="teleport_complete",
|
||||
id=client.id,
|
||||
x=client.x,
|
||||
y=client.y,
|
||||
),
|
||||
exclude=client.websocket,
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(packet, UpdateNicknamePacket):
|
||||
requested_nickname = packet.nickname.strip()
|
||||
if not requested_nickname:
|
||||
|
||||
@@ -135,3 +135,27 @@ async def test_update_position_enforces_cumulative_budget_per_tick(monkeypatch:
|
||||
assert client.x == 7
|
||||
assert client.y == 5
|
||||
assert len(broadcast_payloads) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_teleport_complete_broadcasts_spatial_event(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
server = SignalingServer("127.0.0.1", 8765, None, None, grid_size=41)
|
||||
ws = _fake_ws()
|
||||
client = ClientConnection(websocket=ws, id="u1", nickname="tester", x=12, y=13)
|
||||
server.clients[ws] = client
|
||||
|
||||
broadcast_payloads: list[object] = []
|
||||
|
||||
async def fake_broadcast(packet: object, exclude: ServerConnection | None = None) -> None:
|
||||
broadcast_payloads.append(packet)
|
||||
|
||||
monkeypatch.setattr(server, "_broadcast", fake_broadcast)
|
||||
|
||||
await server._handle_message(client, json.dumps({"type": "teleport_complete"}))
|
||||
|
||||
assert len(broadcast_payloads) == 1
|
||||
packet = broadcast_payloads[0]
|
||||
assert packet.type == "teleport_complete"
|
||||
assert packet.id == "u1"
|
||||
assert packet.x == 12
|
||||
assert packet.y == 13
|
||||
|
||||
Reference in New Issue
Block a user