Skip to content

Клиентская библиотека ​

@webgui/client — JavaScript-библиотека для страниц, работающих внутри мода WebGUI. Один пакет покрывает чистый JS, React, Vue и Svelte.

bash
npm install @webgui/client

React, Vue и Svelte — опциональные peer-зависимости: импорт ядра не тянет ни одну из них, и в проекте на чистом JS пакет не добавляет ничего лишнего.

Заменяет @webgui/react, @webgui/vue и @webgui/svelte

Это были три параллельные реализации одной и той же логики, которые приходилось править синхронно при каждом новом событии мода. Они продолжают работать и теперь реэкспортируют этот пакет — если остаться на них, ничего не сломается, но новые возможности выходят только здесь. См. Переход.

Как это работает ​

Мод внедряет window.webgui в каждую открытую страницу и по мере изменений шлёт CustomEvent'ы на window. Библиотека подписывается при загрузке модуля и отдаёт результат как сторы; настраивать нечего, провайдер монтировать не нужно.

Вне мода — в обычной вкладке, на дев-сервере, при SSR — все сторы отдают null, а все действия ничего не делают. Одна и та же страница работает и там, и там без проверок на каждом вызове.

Чистый JavaScript ​

js
import { clientStore, runCommand, isInMod } from '@webgui/client'

if (isInMod()) {
  clientStore.subscribe(() => {
    const c = clientStore.get()
    document.querySelector('#hp').textContent = `${c.health} / ${c.maxHealth}`
  })
}

document.querySelector('#home').onclick = () => runCommand('spawn')

У каждого стора одни и те же три члена:

ЧленНазначение
subscribe(listener)Регистрирует слушатель без аргументов. Возвращает функцию отписки.
get()Текущее значение или null, пока мод ничего не прислал.
getServerSnapshot()Всегда null — при серверном рендеринге мода нет.

React ​

tsx
import { useWebGUIClient, isInMod, isReady } from '@webgui/client/react'

export function PlayerInfo() {
  const client = useWebGUIClient()

  if (!isInMod())       return <p>Откройте страницу внутри Minecraft.</p>
  if (!isReady(client)) return <p>Подключаемся…</p>

  return <p>Привет, {client!.username}</p>
}

Нужен React 18+. Все хуки построены на useSyncExternalStore — безопасны в concurrent-режиме, провайдер не требуется.

Vue ​

vue
<script setup lang="ts">
import { useWebGUIClient, runCommand } from '@webgui/client/vue'
const client = useWebGUIClient()
</script>

<template>
  <p v-if="client">Привет, {{ client.username }}</p>
  <button @click="runCommand('spawn')">На спавн</button>
</template>

Нужен Vue 3. Composables убираются через onScopeDispose, поэтому безопасны в любом effect scope.

Svelte ​

svelte
<script lang="ts">
  import { webguiClient, runCommand } from '@webgui/client/svelte'
</script>

{#if $webguiClient}
  <p>Привет, {$webguiClient.username}</p>
{/if}
<button on:click={() => runCommand('spawn')}>На спавн</button>

Нужен Svelte 4+. Экспортируются обычные readable-сторы, так что синтаксис $store работает.

Состояние ​

ЯдроReactVueSvelte
clientStoreuseWebGUIClient()useWebGUIClient()webguiClient
entityStoreuseWebGUIEntity()useWebGUIEntity()webguiEntity
deathStoreuseWebGUIDeath()useWebGUIDeath()webguiDeath
selectorStore(fn, eq?)useWebGUISelector(fn, eq?)useWebGUISelector(fn, eq?)webguiSelector(fn, eq?)

Клиент ​

Присылается на 20 TPS, раз в клиентский тик:

ts
interface WebGUIClient {
  playerUuid:  string
  username:    string
  webviewMode: 'GUI_SCREEN' | 'HUD_OVERLAY' | 'NONE'
  dimension:   string                                 // например "minecraft:overworld"
  pos:         { x: number; y: number; z: number }
  look:        { yaw: number; pitch: number }         // поворот головы, градусы
  fov:         number                                 // вертикальный угол обзора, градусы
  lookingAt:   LookingAt                             // на что смотрит игрок
  health:      number
  maxHealth:   number
  food:        number                                 // 0–20
  xpLevel:     number
  gamemode?:   'survival' | 'creative' | 'adventure' | 'spectator'
  server?:     { address?: string; ping?: number }
}

На что смотрит игрок ​

Что находится под перекрестием, обновляется вместе с остальным состоянием клиента. Это собственный ответ игры — тот, которым она рисует контур блока и имя над мобом, — а не второй луч со своим представлением о дальности.

ts
type LookingAt =
  | { type: 'none' }
  | { type: 'block'; block: string; pos: { x: number; y: number; z: number };
      face: 'up' | 'down' | 'north' | 'south' | 'east' | 'west'; distance: number }
  | { type: 'entity'; uuid: string; entityType: string; name: string;
      pos: { x: number; y: number; z: number }; distance: number }
ПолеПримечания
typeВсегда присутствует. none, когда игрок не смотрит ни на что, — поле не исчезает.
blockRegistry-id, например minecraft:gold_block.
posДля блока — целые координаты блока; для сущности — её собственная позиция.
faceСторона блока, в которую пришла линия взгляда.
distanceОт глаз до точки касания — для блока это его грань, поэтому значение примерно на полблока меньше расстояния до центра.

Сущность ​

Заполняется, когда GUI открыт правым кликом по привязанной сущности (см. /webgui bind); null, если открыт командой.

ts
interface WebGUIEntity {
  uuid: string
  type: string                                        // например "minecraft:villager"
  name: string                                        // кастомное имя, иначе имя типа
  pos:  { x: number; y: number; z: number }
}

Смерть ​

Заполняется только на странице, которую сервер назначил экраном смерти. Справочник по полям — webgui:death.

tsx
import { useWebGUIDeath, useRespawn } from '@webgui/client/react'

export function DeathScreen() {
  const death = useWebGUIDeath()
  const respawn = useRespawn()
  if (!death) return null

  return (
    <div>
      <h1>Вы погибли</h1>
      <p>{death.deathMessage}</p>
      {death.canRespawn && <button onClick={respawn}>Возродиться</button>}
    </div>
  )
}

Селекторы ​

Мод присылает состояние клиента 20 раз в секунду, поэтому компонент, которому нужно только health, иначе перерисовывался бы на каждый шаг игрока. Селектор это сужает:

tsx
const health = useWebGUISelector((c) => c.health)

Если селектор строит новый объект, передайте функцию сравнения:

tsx
const pos = useWebGUISelector(
  (c) => ({ x: c.pos.x, z: c.pos.z }),
  (a, b) => a.x === b.x && a.z === b.z,
)

Действия ​

postToGame, closeGui, runCommand и respawn — обычные функции, экспортируются из всех точек входа.

ts
postToGame({ channel: 'shop:buy', item: 'diamond' })   // → на сервер, как page event
closeGui()                                             // закрывает GUI или HUD
runCommand('give @s minecraft:diamond 1')              // выполняется от имени игрока
respawn()                                              // только на кастомном экране смерти

React дополнительно экспортирует usePostToGame, useCloseGui, useRunCommand и useRespawn — те же функции со стабильной идентичностью для массивов зависимостей.

runCommand требует разрешения сервера

Команды принимаются только из главного фрейма origin'а, указанного в trustedCommandOrigins. Отовсюду ещё мод молча отбрасывает запрос. Выполняются с правами самого игрока, так что повышения привилегий нет.

Ограничение частоты

Сервер ограничивает число page-событий на игрока в секунду (pageEventsPerSecond, по умолчанию 20). postToGame, runCommand, closeGui и respawn учитываются все. Не вызывайте их в цикле рендера.

Хелперы ​

ЯдроReactVueSvelte
isInMod()так жетак жетак же
isReady(value)так жетак жетак же
getToken(param?)useWebGUIToken(param?)useWebGUIToken(param?)webguiToken(param?)
onWebGUIEvent(name, fn)useWebGUIEvent(name, fn)useWebGUIEvent(name, fn)onWebGUIEvent(name, fn)

getToken возвращает подписанный токен, который мод дописал в URL страницы, — для проверки на бэкенде. onWebGUIEvent подписывается на именованное событие, отправленное сервером через emitToPage; он возвращает функцию отписки, а версии для фреймворков убираются сами.

Чего браузер не делает ​

Страница работает в Chromium, но это страница внутри игры, а не вкладка браузера:

window.openНичего не делает — открывать окно негде. Открывайте страницы средствами мода.
Скачивание файловРаботает: файл попадает в webgui-downloads/ в папке игры, игроку сообщается имя.
window.webgui.assetsBaseЗадан, когда файлы страницы раздаёт сервер — см. Страницы, которые раздаёт сервер.
window.webgui.isHudtrue, если страница открыта как прозрачный HUD-оверлей, а не полноэкранный GUI. Пригодится, чтобы убрать фон и не навешивать обработку ввода, которую HUD всё равно не получает.
Контекстное меню, печать, горячие клавиши DevToolsНедоступны. Про ошибки — Отладка страницы.

События, которые случаются раньше вашего кода ​

Мод шлёт webgui:death ровно один раз, сразу после загрузки документа, и перед отправкой кладёт window.webgui.client, .entity и .death. Все сторы читают эти снапшоты при импорте модуля, поэтому компонент, смонтировавшийся тиком позже, всё равно увидит значение.

Именно поэтому стоит использовать сторы библиотеки, а не голый addEventListener внутри компонента: такой слушатель вешается уже после того, как событие произошло, и не услышит ничего.

Переход ​

Имена не изменились, поэтому обычно правится только путь импорта:

diff
- import { useWebGUIClient } from '@webgui/react'
+ import { useWebGUIClient } from '@webgui/client/react'
diff
- import { useWebGUIClient } from '@webgui/vue'
+ import { useWebGUIClient } from '@webgui/client/vue'
diff
- import { webguiClient } from '@webgui/svelte'
+ import { webguiClient } from '@webgui/client/svelte'

Смешивать оба варианта во время постепенного перехода безопасно: старые пакеты реэкспортируют этот, поэтому оба пути импорта ведут в один и тот же стор, а не в две копии с разными значениями.