From 6a2944a88ea80d1f3335804f5e32adf6910b6b24 Mon Sep 17 00:00:00 2001 From: scoped Date: Wed, 30 Sep 2026 18:09:18 -0400 Subject: [PATCH] Rework BlueMap claiming and expand ranks guilds chat and warps --- .env.example | 2 - Dockerfile | 17 - docker-compose.yml | 8 - docs/deployment.md | 8 +- docs/live-map.md | 76 +-- docs/ranks-guilds-and-warps.md | 21 + docs/vantage-setup.md | 254 -------- examples/paper/config.yml | 6 +- .../java/net/scopenet/api/ScopenetPlayer.java | 4 +- .../net/scopenet/integration/Integration.java | 15 +- .../net/scopenet/integration/LiveMapSync.java | 379 ++--------- .../java/net/scopenet/minecraft/Bridge.java | 13 +- .../net/scopenet/paper/ChatFormatter.java | 57 ++ .../net/scopenet/paper/ScopenetPlugin.java | 42 +- .../paper/commands/EssentialsHandler.java | 97 +++ .../scopenet/paper/commands/GuildHandler.java | 54 ++ .../scopenet/paper/commands/HandHandler.java | 71 +++ .../paper/commands/NicknameHandler.java | 63 ++ .../paper/compat/LuckPermsModule.java | 1 + .../paper/compat/ScopenetApiImpl.java | 2 +- .../paper/src/main/resources/config.yml | 5 +- .../paper/src/main/resources/plugin.yml | 45 ++ launcher/package-lock.json | 99 +-- launcher/package.json | 5 +- launcher/src-tauri/src/commands.rs | 114 +++- launcher/src-tauri/src/game.rs | 3 + launcher/src-tauri/src/lib.rs | 12 +- launcher/src-tauri/src/state.rs | 2 +- launcher/src/components/BlueMapTiles.svelte | 214 +++++++ launcher/src/components/ChunkMap.svelte | 595 ++++-------------- .../src/components/InstanceSettings.svelte | 9 + launcher/src/components/LiveMapModal.svelte | 131 ---- launcher/src/components/VantageMap.svelte | 208 ------ launcher/src/lib/mock.ts | 4 +- launcher/src/lib/types.ts | 3 +- launcher/src/pages/Commands.svelte | 7 + launcher/src/pages/Guilds.svelte | 110 +++- launcher/src/pages/Home.svelte | 11 +- launcher/src/pages/Settings.svelte | 1 - panel/server/src/config.rs | 9 - panel/server/src/db.rs | 12 + panel/server/src/lib.rs | 2 +- panel/server/src/livemap.rs | 282 +-------- panel/server/src/main.rs | 1 - panel/server/src/routes/dev_api.rs | 4 +- panel/server/src/routes/discord.rs | 43 +- panel/server/src/routes/guilds.rs | 163 ++++- panel/server/src/routes/integrations.rs | 19 +- panel/server/src/routes/leveling.rs | 39 +- panel/server/src/routes/livemap.rs | 72 +-- panel/server/src/routes/map.rs | 10 +- panel/server/src/routes/mod.rs | 6 +- panel/server/src/routes/progression.rs | 4 +- panel/server/src/routes/servers.rs | 20 +- panel/server/tests/common/mod.rs | 3 - panel/server/tests/guild_roles.rs | 37 ++ panel/server/tests/livemap.rs | 90 +-- panel/web/package-lock.json | 99 +-- panel/web/package.json | 5 - panel/web/src/components/LiveMapPanel.svelte | 202 +----- panel/web/src/components/ServerForm.svelte | 11 +- panel/web/src/lib/types.ts | 10 +- panel/web/src/pages/Dashboard.svelte | 17 +- panel/web/src/pages/LevelingAdmin.svelte | 14 +- panel/web/src/pages/ServerDetail.svelte | 4 +- panel/web/src/pages/Settings.svelte | 5 +- release-artifacts/0.5.0/SHA256SUMS | 2 +- .../0.5.0/scopenet-paper-0.5.0.jar | Bin 624557 -> 627793 bytes 68 files changed, 1453 insertions(+), 2500 deletions(-) create mode 100644 docs/ranks-guilds-and-warps.md delete mode 100644 docs/vantage-setup.md create mode 100644 integrations/paper/src/main/java/net/scopenet/paper/ChatFormatter.java create mode 100644 integrations/paper/src/main/java/net/scopenet/paper/commands/HandHandler.java create mode 100644 integrations/paper/src/main/java/net/scopenet/paper/commands/NicknameHandler.java create mode 100644 launcher/src/components/BlueMapTiles.svelte delete mode 100644 launcher/src/components/LiveMapModal.svelte delete mode 100644 launcher/src/components/VantageMap.svelte create mode 100644 panel/server/tests/guild_roles.rs diff --git a/.env.example b/.env.example index 38002c7..d17ae0f 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,4 @@ PUBLIC_URL= # Comma-separated IPs of your reverse proxies; blank means forwarded IP headers are ignored. SCOPENET_TRUSTED_PROXIES= -# Live Map: optional static Vantage Linux release to bundle, and memory for the panel. -VANTAGE_URL= PANEL_MEMORY=256M diff --git a/Dockerfile b/Dockerfile index 483a241..38f8b7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,20 +36,6 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry,sharin # Data dir owned by the non-root runtime user. RUN mkdir -p /out/data && chown 65532:65532 /out/data -# ---- 2b. Vantage generator (optional) ---------------------------------------- -# The live map renders with Vantage. Point VANTAGE_URL at a static Linux -# release archive (tar.gz or zip) for the target arch to bake it into the image; -# leave it empty to mount your own binary at /opt/vantage/vantage instead. -FROM --platform=$BUILDPLATFORM alpine:3 AS vantage -ARG VANTAGE_URL="" -RUN apk add --no-cache curl tar unzip && mkdir -p /out/vantage /out/assets \ - && if [ -n "$VANTAGE_URL" ]; then \ - curl -fsSL "$VANTAGE_URL" -o /tmp/vantage.pkg \ - && (tar -xzf /tmp/vantage.pkg -C /out/vantage 2>/dev/null || unzip -q /tmp/vantage.pkg -d /out/vantage) \ - && find /out/vantage -type f -name vantage -exec chmod 755 {} + \ - && (f=$(find /out/vantage -type f -name vantage | head -n1) && [ -n "$f" ] && [ "$f" != /out/vantage/vantage ] && mv "$f" /out/vantage/vantage || true); \ - fi - # ---- 3. Runtime -------------------------------------------------------------- FROM scratch LABEL org.opencontainers.image.title="SCOPENET Panel" \ @@ -58,12 +44,9 @@ LABEL org.opencontainers.image.title="SCOPENET Panel" \ COPY --from=build /scopenet-panel /scopenet-panel COPY --from=web /src/panel/web/dist /web COPY --from=build --chown=65532:65532 /out/data /data -COPY --from=vantage /out/vantage /opt/vantage ENV SCOPENET_BIND=0.0.0.0:8080 \ SCOPENET_DATA_DIR=/data \ SCOPENET_WEB_DIR=/web \ - SCOPENET_VANTAGE_BIN=/opt/vantage/vantage \ - SCOPENET_VANTAGE_ASSETS=/data/livemap/assets \ RUST_LOG=info USER 65532:65532 VOLUME ["/data"] diff --git a/docker-compose.yml b/docker-compose.yml index 208a2ea..68f8d9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,9 +10,6 @@ services: image: scopenet-panel:local build: context: . - args: - # Optional: URL of a static Vantage Linux release to bundle. - VANTAGE_URL: ${VANTAGE_URL:-} container_name: scopenet-panel restart: unless-stopped ports: @@ -29,10 +26,6 @@ services: MAX_UPLOAD_MB: ${MAX_UPLOAD_MB:-2048} PUBLIC_URL: ${PUBLIC_URL:-} SCOPENET_TRUSTED_PROXIES: ${SCOPENET_TRUSTED_PROXIES:-} - # Live Map: Vantage generator + Minecraft client assets (see docs/live-map.md). - SCOPENET_VANTAGE_BIN: ${SCOPENET_VANTAGE_BIN:-/opt/vantage/vantage} - SCOPENET_VANTAGE_ASSETS: ${SCOPENET_VANTAGE_ASSETS:-/data/livemap/assets} - SCOPENET_VANTAGE_ARGS: ${SCOPENET_VANTAGE_ARGS:-} RUST_LOG: ${RUST_LOG:-info} volumes: - panel-data:/data @@ -40,7 +33,6 @@ services: deploy: resources: limits: - # Raise this if you enable Live Map: Vantage bakes tiles in memory. memory: ${PANEL_MEMORY:-256M} volumes: diff --git a/docs/deployment.md b/docs/deployment.md index f9183b3..81d2ab4 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -23,11 +23,9 @@ The panel listens on `PANEL_PORT` (default `8080`). Docker checks `docker compose ps` before directing players to it. If `ADMIN_PASSWORD` is blank on first start, the generated password appears in the panel logs. -To show a Vantage world map in the launcher, deploy Vantage next to each game -server and configure its manifest URL in the panel. See -[Live map setup](live-map.md). Vantage needs access to the game world's saved -files, so it runs alongside the game server rather than inside the panel -container. +To show a 2D world map and claim land in the launcher, install BlueMap on the +game server and configure its public web address in the panel. See +[Live map setup](live-map.md). The named `panel-data` volume holds the database, uploads, hosted launcher downloads, and the generated JWT signing key. Preserve that volume when diff --git a/docs/live-map.md b/docs/live-map.md index e82f02c..38970a1 100644 --- a/docs/live-map.md +++ b/docs/live-map.md @@ -1,75 +1,21 @@ -# Live Map (central Vantage) +# Live map and land claims -> **Looking for the simplest map?** Use BlueMap: [bluemap.md](bluemap.md). It needs nothing on the panel, adds guild claims, pins and a player panel, and shows inside the launcher. -> -> **Setting up the panel-hosted Vantage map?** Follow [vantage-setup.md](vantage-setup.md). This page is the overview. +SCOPENET uses [BlueMap](https://bluemap.bluecolored.de/) for terrain. Install BlueMap on the Minecraft server and make its web app reachable from players' computers. In the panel's **Servers → server settings**, enter the BlueMap **Map address**. The launcher opens the same map in its Live Map window and reads BlueMap's low resolution 2D tile images for the Guilds territory view. -The panel hosts a [Vantage](https://github.com/thoughts-on-things/vantage-mc) 3D map -**for every game server**, so nobody has to run Vantage next to Minecraft. +The terrain address must serve BlueMap's `settings.json`, per-map `settings.json`, and low resolution PNG tiles from the same URL path. A URL such as `https://maps.example.com/` works. If BlueMap is hosted below a path, include the trailing slash, for example `https://example.com/bluemap/`. -``` -Minecraft server ──(SCOPENET plugin/mod)──► Admin panel ──► Launcher "Live Map" button - chunks + players HTTPS / WebSocket mirror + Vantage Vantage viewer in-app -``` +## Center on the player -1. **Collect** – the SCOPENET Paper plugin / Fabric / Forge mod reads the world's - region files (only chunks newer than what the panel already has) and sends them to - the panel over HTTPS. Player positions stream over a WebSocket (HTTPS fallback). -2. **Store & render** – the panel keeps a region-file mirror per server under - `/livemap//world` and runs a `vantage server` sidecar over it on - demand (stopped again after 15 idle minutes). Sidecars listen on loopback only. -3. **Serve** – `GET /api/livemap///v1/worlds/default/…` proxies the - sidecar to signed-in launcher/admin sessions. Player markers come straight from - memory. One map per server and dimension (`overworld`, `the_nether`, `the_end`). -4. **View** – the launcher checks `GET /api/v1/servers//livemap`; when Live Map is - on for a server linked to the instance, the instance page shows a **Live Map** - button that opens the map inside the launcher (and the Guilds territory view uses - it too). Admins see the same map on *Servers → your server*. +Turn on **Live player positions** in the server settings and leave `livemap.enabled: true` in the SCOPENET plugin or mod configuration. The game server sends current positions to the panel. The panel returns each signed-in player only their own position to the launcher. The **Find me** button centers the territory view on it; the blue dot marks their position. Positions expire when the server stops sending updates. -Chunk data only reaches the panel when the game server has written it, so the map -follows autosaves (and `/save-all`) rather than every block change. +The BlueMap map itself remains available when position relay is off. In that case, players can pan manually, but **Find me** is unavailable. -## Turn it on +## Claim controls -1. **Servers → your server → Settings → Live Map** (on by default for new servers). -2. Update the plugin/mod. `livemap.enabled: true` (Paper `config.yml`) or - `livemap.enabled=true` (`config/scopenet.properties`) is the default; set it to - `false` to keep a server off the map locally. -3. The server page shows a checklist: generator, assets, world data, player positions. +In **Guilds & Territories**, select the game server and dimension. Left click a chunk to claim it for your guild, or drag across chunks to claim an area. Right click or right drag unclaims your guild's chunks. Alt drag pans; the wheel zooms. The overlay shows your guild's claims in green and other guilds' claims in red. The panel checks membership and claim limits for every request; only successful claims appear in the final map state. -## Panel requirements +BlueMap must have rendered the area to show terrain. Claims can still be managed by the in-game `/claim` and `/unclaim` commands when the web map is unavailable. -Rendering needs two things on the machine running the panel: +## Existing world mirror -| Setting | Meaning | -| --- | --- | -| `SCOPENET_VANTAGE_BIN` | The `vantage` executable (default: `vantage` on `PATH`, then `/opt/vantage/vantage`). | -| `SCOPENET_VANTAGE_ASSETS` | Minecraft client assets directory passed as `--assets` (default `/livemap/assets`). | -| `SCOPENET_VANTAGE_ARGS` | Optional extra `vantage server` flags, e.g. `--radius 2048 --memory 1024`. | - -With Docker, either mount your binary at `/opt/vantage/vantage` and the assets at -`/data/livemap/assets`, or bake a static Linux release in with -`VANTAGE_URL= docker compose build`. Until both exist the panel still -stores what servers send and launchers show "the map is being prepared". -Give the container more than the default 256 MB if Live Map is on (`PANEL_MEMORY`). - -## Security - -* Game servers authenticate with their existing `sn_…` token; the endpoints refuse - servers whose Live Map switch is off. -* Viewers must be signed in; the launcher attaches its session token natively, so it - never reaches page scripts. Anyone with an account can view a server's map — use - the server's access rules and leave Live Map off for private worlds. -* The sidecar gets a random per-process bearer token and never leaves loopback. - -## Ingest API (for reference) - -All under `/api/server/v1/livemap/` with `Authorization: Bearer `: -`GET config`, `GET manifest?dim=`, `POST chunks?dim=` (binary records of -`i32 x, i32 z, u32 timestamp, u8 compression, u32 length, bytes`), `POST level` -(`level.dat`), `POST players`, `GET ws` (WebSocket: JSON `players` frames). - -## External Vantage (advanced) - -Under *Use an external Vantage map instead* you can still point a server at a -`manifest.json` / `world.json` you host yourself. It is only used when Live Map is off. +Older SCOPENET game integrations uploaded region chunks to the panel under `/api/server/v1/livemap`. The panel retains those ingest endpoints for compatibility with existing servers, but the launcher terrain comes from BlueMap. Current integrations send player positions only. diff --git a/docs/ranks-guilds-and-warps.md b/docs/ranks-guilds-and-warps.md new file mode 100644 index 0000000..97a9df9 --- /dev/null +++ b/docs/ranks-guilds-and-warps.md @@ -0,0 +1,21 @@ +# Ranks, guild roles, chat and warps + +## Level ranks + +In the panel's **Leveling** page, create a **Player Title / Prefix** reward at each global level milestone. A title can name a LuckPerms group and a Discord role ID. The highest title the player has earned is shown in Paper chat and, when LuckPerms is installed, its configured group is assigned automatically. SCOPENET only changes LuckPerms groups explicitly named on title rewards. Existing panel group to LuckPerms mappings still work independently. + +In **Settings → Discord community**, enable role sync and optionally set a base role ID. Linked players receive the Discord role for their highest earned title; players without a mapped title receive the base role. When a higher title replaces a lower one, the old managed title role is removed. Players without a linked Discord account cannot receive a Discord role; in-game chat shows the base `Novice` title until they earn a title. + +Paper chat shows `[Guild tag] [Level title] [LuckPerms group display] nickname: message`. LuckPerms prefixes and suffixes decorate the name. Messages support `**bold**`, `*italic*`, `__underline__`, `~~strikethrough~~`, backtick code, and Bukkit `&` formatting. `&#RRGGBB` colors and `&` codes require `scopenet.chat.color`, which is granted to operators by default. Players set or clear a persistent nickname with `/nick `; chat also respects display names set by other plugins. + +## Guilds + +Players can request to join a guild from the launcher directory. Leaders and officers review those requests from the guild roster. Leaders can edit the guild description, message of the day, icon and banner from **Guild settings**. They can create and assign custom roles with permissions for invites, kicks, claims, posts and guild details. Deleting a role returns its members to `member`. + +The in-app territory view uses the configured BlueMap 2D tile address for each server. It centers on the player's last position when position relay is enabled. Left click or drag to claim; right click or drag to unclaim. On Paper, `/autoclaim on` claims each new unclaimed chunk entered while walking; `/autoclaim off` stops it. Auto-claim stops on a failed claim so it does not repeatedly send requests at the claim limit or in another guild's territory. + +## Warps and item sharing on Paper + +Players use `/reqwarp ` at the requested location. An operator or player with `scopenet.command.warp.manage` uses `/warprequests`, then `/warpapprove ` or `/warpdeny `. Admins can create and remove warps directly with `/setwarp ` and `/delwarp `. Pending requests are stored in `plugins/SCOPENET/warp_requests.yml`; approved warps are stored in `warps.yml`. + +`/hand` sends the held item as a clickable chat entry. Other players can inspect a read-only snapshot for five minutes. The launcher **Command Guide** lists these commands and their permission nodes. diff --git a/docs/vantage-setup.md b/docs/vantage-setup.md deleted file mode 100644 index df3cb38..0000000 --- a/docs/vantage-setup.md +++ /dev/null @@ -1,254 +0,0 @@ -# Setting up the Vantage 3D Live Map - -This is the complete, step-by-step guide. For a short overview of how the pieces fit, see [live-map.md](live-map.md). - -**What you end up with:** every SCOPENET-connected server gets a 3D map inside the admin panel and a **Live Map** button in the launcher. You install Vantage **once, on the panel machine**. Nothing runs next to your Minecraft servers except the SCOPENET plugin or mod you already use. - -``` -Minecraft server ──(SCOPENET plugin/mod)──► Panel ──runs──► vantage server ──► Admin page + Launcher button - region files + players HTTPS / WebSocket mirror of the world (loopback only) -``` - -> **Two things you must provide on the panel machine:** the `vantage` program, and the Minecraft **client assets** (textures and models) that Vantage needs to draw blocks. The panel will not draw a map until both exist. Everything else works out of the box. - ---- - -## 0. Before you start - -| You need | Notes | -| --- | --- | -| A running SCOPENET panel | With a **Public address** set in Settings, served over HTTPS. | -| A game server running the SCOPENET plugin/mod | Paper plugin, or the Fabric/Forge mod. The server must be online with `online-mode=true` (the same requirement as SCOPENET itself). | -| RAM | Allow **1 GB or more** for the panel when Live Map is on (the default 256 MB is too small). See [sizing](#7-sizing-and-performance). | -| Disk | Roughly the size of the world's region files, plus the rendered tile cache (often the same again). | -| A copy of Minecraft Java Edition | Needed **once**, to extract assets (step 2). It can be on any computer. | - -Vantage is an MIT-licensed third-party project: (product site ). Its own server documentation is the authority on its flags. - ---- - -## 1. Get the `vantage` program - -Download the latest **CLI release** for the panel machine's operating system and CPU from the Vantage releases page: . - -* **Docker panel image:** the image has no shell and no libraries (it is built `FROM scratch`), so it needs a **statically linked Linux build** of `vantage` for the right architecture (x86_64 or arm64). If the release you pick is not static, it will fail to start inside the container with a "not found" error. In that case run the panel on the host, or use a static build. -* **Bare-metal panel:** any build that runs on your OS works. - -Put the file somewhere permanent and make it executable (`chmod 755 vantage`). Check it runs: - -```sh -./vantage --help -``` - ---- - -## 2. Get the Minecraft client assets - -Vantage draws blocks from the textures and models inside Minecraft's client `.jar`. You extract them once with Vantage's own command: - -```sh -vantage extract -``` - -It automatically finds an installed Minecraft client jar (the launcher's `versions//.jar`) and writes the assets out. Run `vantage extract --help` for the flags (for example to choose the jar or the output folder). - -**Which version?** Use a client jar for the same Minecraft version your servers run, or a newer one. Newer is safer if the world contains newer blocks. - -**Running extract on a different computer is fine.** Do it on your own PC (which has Minecraft installed), then copy the resulting folder to the panel machine. You do not need Minecraft on the server. - -**Where the panel looks:** it passes the folder in `SCOPENET_VANTAGE_ASSETS` (default `/livemap/assets`) to `vantage server --assets`. Vantage's own examples point `--assets` at a folder ending in `assets/minecraft`. So **copy the extracted folder to the panel, then point the setting at the folder Vantage expects**. If unsure, test it directly (this command should start and print that it is listening, then stop it with Ctrl+C): - -```sh -vantage server /path/to/any/world --assets /path/to/your/assets/folder --out /tmp/vantage-test -``` - -If Vantage complains about missing assets, try the parent or the `minecraft` subfolder and use whichever works. - -> **Licence note:** the assets are Mojang's. Extract them from a client you own, and do not redistribute them. - ---- - -## 3. Install them on the panel - -Pick **one** of the two layouts. - -### Option A: Docker (recommended) - -The provided `docker-compose.yml` already defines the three settings. Mount your files into the data volume or bake Vantage into the image. - -**A1. Mount the files** (simplest; no rebuild). Put the binary and assets on the Docker host, then add volumes to the `panel` service in `docker-compose.yml`: - -```yaml - volumes: - - panel-data:/data - - ./vantage/vantage:/opt/vantage/vantage:ro # the static binary from step 1 - - ./vantage/assets:/data/livemap/assets:ro # the folder from step 2 -``` - -`SCOPENET_VANTAGE_BIN` (`/opt/vantage/vantage`) and `SCOPENET_VANTAGE_ASSETS` (`/data/livemap/assets`) already default to those paths. - -**A2. Bake the binary in** (assets still come from a mount): - -```sh -VANTAGE_URL="https://…/vantage-x86_64-unknown-linux-musl.tar.gz" docker compose build -``` - -`VANTAGE_URL` must be a direct link to a `.tar.gz` or `.zip` containing the `vantage` file for the **build machine's target architecture**. - -**Raise the memory limit** in `.env` (or the compose file): - -```env -PANEL_MEMORY=1536M -``` - -Then recreate the container: - -```sh -docker compose up -d --build -``` - -### Option B: Panel running directly on the host - -```sh -export SCOPENET_VANTAGE_BIN=/opt/vantage/vantage -export SCOPENET_VANTAGE_ASSETS=/var/lib/scopenet/livemap/assets -# Optional extra flags for `vantage server`: -export SCOPENET_VANTAGE_ARGS="--memory 1024 --threads 4" -``` - -If you leave the two variables unset, the panel looks for `vantage` on `PATH` and then `/opt/vantage/vantage`, and for assets in `/livemap/assets`. With systemd, put these in the unit's `Environment=` lines. - ---- - -## 4. Turn the map on for a server - -1. In the panel: **Servers → your server → Settings → Live Map** (on by default for new servers). Save. -2. On the game server, make sure the plugin/mod has Live Map enabled. It is on by default: - * **Paper** `plugins/SCOPENET/config.yml`: `livemap.enabled: true` - * **Fabric/Forge** `config/scopenet.properties`: `livemap.enabled=true` -3. **Restart the game server** (or reload the SCOPENET config) so it starts sending data. - -The plugin reads the world's saved region files, sends only chunks newer than what the panel already has, and streams player positions every second. - -**World location:** the plugin reads the main world's folder (the first world Paper loads, normally `world`), and the Fabric/Forge mod reads `level-name` from `server.properties`. The Nether and End are found either inside that folder (`DIM-1`, `DIM1`) or in Paper's sibling folders (`world_nether`, `world_the_end`). - ---- - -## 5. Check that it works - -Open **Servers → your server**. The Live Map card shows four checks: - -| Check | Green when | If it is red | -| --- | --- | --- | -| **Vantage generator** | The panel found the `vantage` file. | Fix `SCOPENET_VANTAGE_BIN` / the mount (step 3). | -| **Minecraft assets** | The assets folder exists. | Fix `SCOPENET_VANTAGE_ASSETS` (step 2–3). | -| **World data** | Region files have arrived. | Wait for the server to upload. Big worlds take a while the first time. | -| **Player positions** | Players are online and the server is sending them. | Have someone join; positions only flow while the server runs the plugin/mod. | - -When all are green the map appears under **Overworld / Nether / End** tabs. In the launcher, the instance linked to that server shows a **Live Map** button. (The instance must be linked to the server in the panel, under the server's **Launcher instance** setting.) - -**How fresh is it?** Chunk data only reaches the panel once the game server has *written it to disk*. The map therefore follows the server's autosaves (every few minutes by default). To refresh right now, run `/save-all` on the game server. Player positions are real-time. - ---- - -## 6. How it behaves (good to know) - -* **Sidecars start on demand.** The panel starts one `vantage server` per server and dimension when someone opens a map, and **stops it after 15 idle minutes**. The first open after idle takes a few seconds. -* **Loopback only.** The sidecars listen on `127.0.0.1` with a random per-process token. They are never exposed. All map traffic goes through the panel and needs a signed-in session. -* **The world mirror** lives in `/livemap//world`, and rendered tiles in a cache beside it. **Servers → Live Map → Reset map data** deletes both; the game server then uploads everything again. -* **Pre-baking.** Vantage renders tiles in the background around spawn and players, so the map gets smoother after the first visit. - ---- - -## 7. Sizing and performance - -Put extra `vantage server` flags in `SCOPENET_VANTAGE_ARGS` (space separated). The useful ones: - -| Flag | What it does | -| --- | --- | -| `--memory ` | Memory budget for rendering. Start at `1024`. | -| `--threads ` | Maximum parallel tile renders. Give at least `2`. | -| `--radius ` | Limit how far from spawn the map extends. Saves CPU and disk on huge worlds. | -| `--prebake off` | Only render what people look at. Lower CPU, slower first views. | -| `--lod off` | Skip the zoomed-out overview. Smaller cache, but the map is only visible where tiles are loaded. | -| `--scan-interval ` | How often it checks for changed regions (default `5`). | -| `--caves`, `--light`, `--biome-blend` | Render quality options. See Vantage's docs. | - -Rules of thumb: - -* **1 server, modest world:** 1 GB panel memory, `--threads 2`. -* **Several servers:** each open map dimension is its own process, and they stop after 15 idle minutes. Budget memory for the number of maps opened at once, not the number of servers. -* **Huge worlds:** set `--radius`, or `--prebake off`, and be patient on the first upload. -* **Disk:** watch `/livemap`. **Reset map data** frees it. - ---- - -## 8. Reverse proxy and HTTPS - -The map is served from the panel's own address, so no new ports are needed. If the panel sits behind nginx, Caddy, Traefik or Cloudflare: - -* Forward **WebSocket upgrades** to `/api/server/v1/livemap/ws`. The plugin falls back to plain HTTPS if WebSockets are blocked, but positions update faster with them. -* Allow **large request bodies** for chunk uploads (`client_max_body_size 64m;` in nginx). -* Do not cache `/api/livemap/` responses. -* Keep the panel's **Public address** set to the HTTPS URL players use. - ---- - -## 9. Privacy and access - -* Anyone with a **signed-in account** can open a server's map. To keep a world private, leave Live Map **off** for that server, or restrict the server with its access rules. -* The panel feeds Vantage **only the players who are online right now** (positions from the server plugin/mod). It does **not** expose the last-known positions of everyone who has ever logged in. -* Game servers authenticate with their normal `sn_…` token; servers with Live Map off are refused. - ---- - -## 10. Troubleshooting - -| You see | Cause and fix | -| --- | --- | -| Generator "Not installed on the panel" | The panel can't find `vantage`. Check `SCOPENET_VANTAGE_BIN`, the volume mount, and that the file is executable. In Docker, check the file is a **static** Linux build for the right CPU. | -| "Minecraft client assets for Vantage aren't configured" | The assets folder is missing or the path is wrong. Re-check step 2–3 and that the folder is readable by the container user (uid 65532). | -| "Waiting for the game server to upload this world" | The panel has no `level.dat` or region files for that dimension yet. Check the plugin/mod is connected (server page shows Online), Live Map is on in **both** the panel and the plugin config, and wait for an autosave or run `/save-all`. | -| Launcher shows "the map is being prepared" | Same as above, or the generator/assets check is red. | -| "couldn't start the Vantage generator" | The file isn't executable, is for another CPU/OS, or (in Docker) isn't static. Try running it by hand. | -| Map loads but is blank or shows pink/black blocks | Wrong or incomplete assets. Re-extract from a client jar of the right version. | -| Map is out of date | Chunks appear only after the server saves. Run `/save-all`, or lower the autosave interval in the server settings. | -| Some dimensions are greyed out | That dimension has no region data yet. Visit it in-game and save. | -| Everything is slow | Raise `PANEL_MEMORY` and `--threads`, set `--radius`, or use `--prebake off`. | -| No players on the map | Positions only arrive while the plugin/mod is running and someone is online. Confirm the "Player positions" check. | -| Panel logs: generator stopped | Normal after 15 idle minutes. It restarts on the next open. | - -Still stuck? Open the server page, read **"Generator: …"** under the checklist (it shows the last error from Vantage), and check the panel logs with `RUST_LOG=debug`. - ---- - -## 11. Reference - -**Panel settings (environment variables)** - -| Variable | Default | Meaning | -| --- | --- | --- | -| `SCOPENET_VANTAGE_BIN` | `vantage` on `PATH`, then `/opt/vantage/vantage` | The Vantage executable. | -| `SCOPENET_VANTAGE_ASSETS` | `/livemap/assets` | Extracted Minecraft client assets. | -| `SCOPENET_VANTAGE_ARGS` | none | Extra flags appended to `vantage server`. | -| `PANEL_MEMORY` (Docker) | `256M` | Container memory limit. Use `1G` or more for Live Map. | -| `VANTAGE_URL` (Docker build arg) | empty | Direct link to a static Vantage release to bake into the image. | - -**Game server settings** - -| Where | Key | Default | -| --- | --- | --- | -| Paper `config.yml` | `livemap.enabled` | `true` | -| Fabric/Forge `config/scopenet.properties` | `livemap.enabled` | `true` | -| Panel, per server | Live Map switch | on for new servers | - -**What the panel runs for each map** (for reference): - -``` -vantage server /livemap//world --assets --out \ - --host 127.0.0.1 --port --players-file --dimension -``` - -with the secret passed in `VANTAGE_SERVER_TOKEN`. - -**Not verified here:** the exact names of Vantage's release files and the exact folder layout that `vantage extract` produces change between Vantage versions. This guide was written against Vantage's published README and server documentation; follow Vantage's own `--help` if they differ. diff --git a/examples/paper/config.yml b/examples/paper/config.yml index bcf6ab3..600a84d 100644 --- a/examples/paper/config.yml +++ b/examples/paper/config.yml @@ -64,10 +64,8 @@ essentials: rtp-radius: 2500 livemap: - # Send world chunks and player positions to the panel's central 3D Live Map. - # Nothing runs on this server; the panel renders it. Needs "Live Map" switched on - # for this server in the panel, and Vantage installed on the panel. - # Setup guide: docs/vantage-setup.md + # Send live player positions to the panel so the launcher can center its + # BlueMap territory view. Configure BlueMap's public address in the panel. enabled: true economy: diff --git a/integrations/api/src/main/java/net/scopenet/api/ScopenetPlayer.java b/integrations/api/src/main/java/net/scopenet/api/ScopenetPlayer.java index 3141c2b..ac7955d 100644 --- a/integrations/api/src/main/java/net/scopenet/api/ScopenetPlayer.java +++ b/integrations/api/src/main/java/net/scopenet/api/ScopenetPlayer.java @@ -36,7 +36,9 @@ public record ScopenetPlayer( QuestProgress weeklyQuests, String joined, String rankGroup, - String rankPrefix) { + String rankDisplay, + String rankPrefix, + String rankSuffix) { public boolean inGuild() { return guild != null; } diff --git a/integrations/common/src/main/java/net/scopenet/integration/Integration.java b/integrations/common/src/main/java/net/scopenet/integration/Integration.java index aca37ac..6c144fc 100644 --- a/integrations/common/src/main/java/net/scopenet/integration/Integration.java +++ b/integrations/common/src/main/java/net/scopenet/integration/Integration.java @@ -144,13 +144,10 @@ public final class Integration implements AutoCloseable { this.notificationHandler = handler; } - /** - * Start mirroring this server's world and player positions to the panel's - * central live map. {@code worldRoot} is the level folder (e.g. {@code world}). - */ - public void enableLiveMap(java.nio.file.Path worldRoot) { + /** Start sending player positions for the in-app BlueMap territory view. */ + public void enableLiveMap() { if (closed || liveMap != null) return; - liveMap = new LiveMapSync(client, worldRoot, log); + liveMap = new LiveMapSync(client); liveMap.start(); } @@ -160,12 +157,6 @@ public final class Integration implements AutoCloseable { if (map != null) map.offerPlayers(players); } - /** The world was just saved: pick up its changed chunks soon. */ - public void liveMapSaved() { - LiveMapSync map = liveMap; - if (map != null) map.requestScan(); - } - public ChunkCheckResult checkChunk(String dimension, int chunkX, int chunkZ, UUID uuid) { return client.checkChunk(dimension, chunkX, chunkZ, uuid); } diff --git a/integrations/common/src/main/java/net/scopenet/integration/LiveMapSync.java b/integrations/common/src/main/java/net/scopenet/integration/LiveMapSync.java index 3d73045..0657a8d 100644 --- a/integrations/common/src/main/java/net/scopenet/integration/LiveMapSync.java +++ b/integrations/common/src/main/java/net/scopenet/integration/LiveMapSync.java @@ -1,363 +1,70 @@ package net.scopenet.integration; -import com.google.gson.*; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URI; -import java.net.http.WebSocket; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.*; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -/** - * Mirrors the world to the panel's central Live Map: changed chunks (raw, - * already-compressed Anvil payloads read straight from the region files) over - * HTTPS, and live player positions over a WebSocket (HTTPS as fallback). - * Nothing here touches the server thread except {@link #offerPlayers}. - */ +/** Sends live player positions to the panel for the in-app BlueMap territory view. */ public final class LiveMapSync implements AutoCloseable { public record Player(String uuid, String name, String dimension, double x, double y, double z, float yaw, float pitch) {} - private static final String[] DIMS = {"overworld", "the_nether", "the_end"}; - private static final int SECTOR = 4096; - private static final int BATCH_BYTES = 6 * 1024 * 1024; - private static final int SCAN_BUDGET_BYTES = 48 * 1024 * 1024; - private final PanelClient client; - private final Path root; - private final Consumer log; - private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(r -> { - Thread t = new Thread(r, "scopenet-livemap"); - t.setDaemon(true); - return t; + private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(task -> { + Thread thread = new Thread(task, "scopenet-positions"); + thread.setDaemon(true); + return thread; }); - /** Per region, the timestamp the panel already holds for each of its 1024 chunks. */ - private final Map known = new HashMap<>(); - private final Set manifestLoaded = new HashSet<>(); - private final AtomicBoolean scanQueued = new AtomicBoolean(); - private volatile boolean panelEnabled; - private volatile boolean closed; private volatile List players = List.of(); - private volatile boolean playersChanged; - private volatile WebSocket socket; - private volatile long socketRetryAt; - private long nextScan; - private long configCheckAt; - private long levelModified = -1; - private long lastPlayersSent; - private long chunkIntervalSecs = 20; - private boolean warnedMissing; + private volatile boolean changed; + private volatile boolean closed; + private long lastSent; - public LiveMapSync(PanelClient client, Path root, Consumer log) { - this.client = client; - this.root = root; - this.log = log; - } + public LiveMapSync(PanelClient client) { this.client = client; } - public void start() { - worker.scheduleWithFixedDelay(this::scanSafely, 10, 5, TimeUnit.SECONDS); - worker.scheduleWithFixedDelay(this::sendPlayersSafely, 5, 1, TimeUnit.SECONDS); - } + public void start() { worker.scheduleWithFixedDelay(this::sendSafely, 1, 1, TimeUnit.SECONDS); } public void offerPlayers(List next) { if (!next.equals(players)) { players = List.copyOf(next); - playersChanged = true; + changed = true; } } - /** A save just happened; scan soon instead of waiting for the next interval. */ - public void requestScan() { - if (closed || !scanQueued.compareAndSet(false, true)) return; + private void sendSafely() { + if (closed) return; + long now = System.nanoTime(); + if (!changed && now - lastSent < TimeUnit.SECONDS.toNanos(10)) return; + JsonArray list = new JsonArray(); + for (Player p : players) { + JsonObject entry = new JsonObject(); + entry.addProperty("uuid", p.uuid()); + entry.addProperty("name", p.name()); + entry.addProperty("dimension", p.dimension()); + entry.addProperty("x", p.x()); + entry.addProperty("y", p.y()); + entry.addProperty("z", p.z()); + entry.addProperty("yaw", p.yaw()); + entry.addProperty("pitch", p.pitch()); + list.add(entry); + } + JsonObject body = new JsonObject(); + body.add("players", list); try { - worker.schedule(() -> { - scanQueued.set(false); - nextScan = 0; - scanSafely(); - }, 3, TimeUnit.SECONDS); - } catch (RejectedExecutionException e) { - scanQueued.set(false); + client.post("livemap/players", body); + changed = false; + lastSent = now; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception ignored) { + // Keep the latest snapshot and retry on the next interval. } } @Override public void close() { closed = true; - WebSocket ws = socket; - if (ws != null) ws.abort(); worker.shutdownNow(); } - - // ---- chunks ---- - - private void scanSafely() { - if (closed || System.nanoTime() < nextScan) return; - try { - if (!refreshEnabled()) return; - long uploaded = scan(); - nextScan = System.nanoTime() + TimeUnit.SECONDS.toNanos(uploaded >= SCAN_BUDGET_BYTES ? 1 : chunkIntervalSecs); - } catch (PanelClient.HttpFailure e) { - if (e.status == 403) panelEnabled = false; - else log.accept("SCOPENET live map: the panel refused an upload (" + e.getMessage() + ")"); - nextScan = System.nanoTime() + TimeUnit.SECONDS.toNanos(30); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - log.accept("SCOPENET live map sync failed: " + e.getClass().getSimpleName() + ": " + e.getMessage()); - nextScan = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); - } - } - - /** True when the panel wants this server's map. Rechecks once a minute while it doesn't. */ - private boolean refreshEnabled() throws IOException, InterruptedException { - if (panelEnabled) return true; - long now = System.nanoTime(); - if (now < configCheckAt) return false; - configCheckAt = now + TimeUnit.SECONDS.toNanos(60); - try { - JsonObject config = client.get("livemap/config"); - panelEnabled = config.has("enabled") && config.get("enabled").getAsBoolean(); - if (config.has("chunk_interval_secs")) chunkIntervalSecs = Math.max(5, config.get("chunk_interval_secs").getAsLong()); - if (panelEnabled) log.accept("SCOPENET live map is on: sending this world to the panel."); - } catch (PanelClient.HttpFailure e) { - panelEnabled = false; // an older panel without live map support - } - return panelEnabled; - } - - /** Returns how many bytes of chunk data were uploaded. */ - private long scan() throws IOException, InterruptedException { - if (!Files.isDirectory(root)) { - if (!warnedMissing) { - warnedMissing = true; - log.accept("SCOPENET live map: world folder " + root + " not found"); - } - return 0; - } - uploadLevelIfChanged(); - long total = 0; - for (String dim : DIMS) { - Path dir = regionDir(dim); - if (dir == null) continue; - loadManifest(dim); - Batch batch = new Batch(dim); - List files = new ArrayList<>(); - try (DirectoryStream stream = Files.newDirectoryStream(dir, "r.*.*.mca")) { - stream.forEach(files::add); - } - files.sort(Comparator.comparing(Path::toString)); - for (Path file : files) { - if (closed || total + batch.size >= SCAN_BUDGET_BYTES) break; - total += readRegion(dim, file, batch); - } - total += batch.flush(); - if (total >= SCAN_BUDGET_BYTES) break; - } - return total; - } - - private Path regionDir(String dim) { - String name = root.getFileName() == null ? "world" : root.getFileName().toString(); - Path parent = root.toAbsolutePath().getParent(); - List candidates = switch (dim) { - case "the_nether" -> Arrays.asList(root.resolve("DIM-1/region"), sibling(parent, name + "_nether", "DIM-1/region"), - root.resolve("dimensions/minecraft/the_nether/region")); - case "the_end" -> Arrays.asList(root.resolve("DIM1/region"), sibling(parent, name + "_the_end", "DIM1/region"), - root.resolve("dimensions/minecraft/the_end/region")); - default -> List.of(root.resolve("region"), root.resolve("dimensions/minecraft/overworld/region")); - }; - for (Path p : candidates) if (p != null && Files.isDirectory(p)) return p; - return null; - } - - private static Path sibling(Path parent, String name, String rel) { - return parent == null ? null : parent.resolve(name).resolve(rel); - } - - private void loadManifest(String dim) throws IOException, InterruptedException { - if (manifestLoaded.contains(dim)) return; - JsonObject manifest = client.get("livemap/manifest?dim=" + dim); - for (JsonElement el : manifest.getAsJsonArray("regions")) { - JsonObject region = el.getAsJsonObject(); - byte[] raw = Base64.getDecoder().decode(region.get("timestamps").getAsString()); - int[] ts = new int[1024]; - ByteBuffer.wrap(raw).asIntBuffer().get(ts, 0, Math.min(1024, raw.length / 4)); - known.put(key(dim, region.get("x").getAsInt(), region.get("z").getAsInt()), ts); - } - manifestLoaded.add(dim); - } - - private static String key(String dim, int rx, int rz) { - return dim + ":" + rx + ":" + rz; - } - - /** Adds every newer chunk of one region file to the batch; returns bytes uploaded by flushes. */ - private long readRegion(String dim, Path file, Batch batch) throws IOException, InterruptedException { - String[] parts = file.getFileName().toString().split("\\."); - int rx, rz; - try { - rx = Integer.parseInt(parts[1]); - rz = Integer.parseInt(parts[2]); - } catch (RuntimeException e) { - return 0; - } - long flushed = 0; - int[] have = known.getOrDefault(key(dim, rx, rz), new int[1024]); - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { - long size = ch.size(); - if (size < 2L * SECTOR) return 0; - ByteBuffer header = ByteBuffer.allocate(2 * SECTOR); - while (header.hasRemaining()) if (ch.read(header, header.position()) < 0) return 0; - for (int i = 0; i < 1024; i++) { - int loc = header.getInt(i * 4); - int ts = header.getInt(SECTOR + i * 4); - int count = loc & 0xff; - long offset = (loc >>> 8) & 0xffffffL; - if (count == 0 || Integer.toUnsignedLong(ts) <= Integer.toUnsignedLong(have[i])) continue; - if (offset < 2 || (offset + count) * SECTOR > size) continue; - ByteBuffer prefix = ByteBuffer.allocate(5); - while (prefix.hasRemaining()) if (ch.read(prefix, offset * SECTOR + prefix.position()) < 0) break; - if (prefix.hasRemaining()) continue; - int length = prefix.getInt(0); - int compression = prefix.get(4) & 0xff; - // Oversized chunks live in external .mcc files; skip those and anything malformed. - if (length < 2 || length - 1 > count * SECTOR - 5 || (compression & 0x80) != 0 || compression == 0 || compression > 4) continue; - ByteBuffer data = ByteBuffer.allocate(length - 1); - while (data.hasRemaining()) if (ch.read(data, offset * SECTOR + 5 + data.position()) < 0) break; - if (data.hasRemaining()) continue; - batch.add(rx, rz, i, rx * 32 + (i & 31), rz * 32 + (i >> 5), ts, compression, data.array()); - if (batch.size >= BATCH_BYTES) flushed += batch.flush(); - } - } - return flushed; - } - - /** Chunk records waiting to be uploaded; a chunk counts as sent only once the panel accepted it. */ - private final class Batch { - final String dim; - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final List marks = new ArrayList<>(); - int size; - - Batch(String dim) { - this.dim = dim; - } - - void add(int rx, int rz, int index, int x, int z, int ts, int compression, byte[] data) { - ByteBuffer rec = ByteBuffer.allocate(17 + data.length); - rec.putInt(x).putInt(z).putInt(ts).put((byte) compression).putInt(data.length).put(data); - out.write(rec.array(), 0, rec.capacity()); - marks.add(new int[]{rx, rz, index, ts}); - size += rec.capacity(); - } - - long flush() throws IOException, InterruptedException { - if (marks.isEmpty()) return 0; - long sent = size; - try { - client.postBytes("livemap/chunks?dim=" + dim, out.toByteArray()); - for (int[] m : marks) known.computeIfAbsent(key(dim, m[0], m[1]), k -> new int[1024])[m[2]] = m[3]; - } finally { - out.reset(); - marks.clear(); - size = 0; - } - return sent; - } - } - - private void uploadLevelIfChanged() throws IOException, InterruptedException { - Path level = root.resolve("level.dat"); - if (!Files.isRegularFile(level)) return; - long modified = Files.getLastModifiedTime(level).toMillis(); - if (modified == levelModified) return; - byte[] bytes = Files.readAllBytes(level); - if (bytes.length > 2 * 1024 * 1024) return; - client.postBytes("livemap/level", bytes); - levelModified = modified; - } - - // ---- players ---- - - private void sendPlayersSafely() { - if (closed || !panelEnabled) return; - long now = System.nanoTime(); - // Resend unchanged rosters now and then so the panel knows we're alive. - boolean heartbeat = now - lastPlayersSent > TimeUnit.SECONDS.toNanos(10); - if (!playersChanged && !heartbeat) return; - playersChanged = false; - lastPlayersSent = now; - JsonArray list = new JsonArray(); - for (Player p : players) { - JsonObject o = new JsonObject(); - o.addProperty("uuid", p.uuid()); - o.addProperty("name", p.name()); - o.addProperty("dimension", p.dimension()); - o.addProperty("x", p.x()); - o.addProperty("y", p.y()); - o.addProperty("z", p.z()); - o.addProperty("yaw", p.yaw()); - o.addProperty("pitch", p.pitch()); - list.add(o); - } - JsonObject body = new JsonObject(); - body.addProperty("type", "players"); - body.add("players", list); - try { - WebSocket ws = socket; - if (ws != null && !ws.isOutputClosed()) { - ws.sendText(body.toString(), true).get(5, TimeUnit.SECONDS); - return; - } - connectSocket(); - client.post("livemap/players", body); - } catch (PanelClient.HttpFailure e) { - if (e.status == 403) panelEnabled = false; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - socket = null; // reconnect later; HTTPS covers the gap - } - } - - private void connectSocket() { - long now = System.nanoTime(); - if (now < socketRetryAt) return; - socketRetryAt = now + TimeUnit.SECONDS.toNanos(30); - try { - Settings settings = client.settings(); - URI panel = settings.panel(); - String scheme = "https".equals(panel.getScheme()) ? "wss" : "ws"; - String path = panel.getRawPath() == null ? "" : panel.getRawPath(); - URI uri = URI.create(scheme + "://" + panel.getRawAuthority() + path + "/api/server/v1/livemap/ws"); - client.http().newWebSocketBuilder() - .header("Authorization", "Bearer " + settings.token()) - .connectTimeout(Duration.ofSeconds(5)) - .buildAsync(uri, new WebSocket.Listener() { - @Override public CompletionStage onText(WebSocket ws, CharSequence data, boolean last) { - if (data.toString().contains("\"disabled\"")) panelEnabled = false; - ws.request(1); - return null; - } - - @Override public CompletionStage onClose(WebSocket ws, int code, String reason) { - if (socket == ws) socket = null; - return null; - } - - @Override public void onError(WebSocket ws, Throwable error) { - if (socket == ws) socket = null; - } - }).thenAccept(ws -> { - if (closed) ws.abort(); - else socket = ws; - }); - } catch (RuntimeException ignored) { - // The HTTPS fallback keeps working. - } - } } diff --git a/integrations/minecraft/src/main/java/net/scopenet/minecraft/Bridge.java b/integrations/minecraft/src/main/java/net/scopenet/minecraft/Bridge.java index 8ddf3fd..ff6a8c3 100644 --- a/integrations/minecraft/src/main/java/net/scopenet/minecraft/Bridge.java +++ b/integrations/minecraft/src/main/java/net/scopenet/minecraft/Bridge.java @@ -33,24 +33,13 @@ public final class Bridge { })); integration.startClaimSync(); Path config = Path.of("config/scopenet.properties"); - if (Settings.liveMapEnabled(config)) integration.enableLiveMap(worldFolder()); + if (Settings.liveMapEnabled(config)) integration.enableLiveMap(); } catch (Exception e) { // Throwing aborts startup; silently disabling an auth mod is unsafe. throw new IllegalStateException("SCOPENET cannot start: " + e.getMessage(), e); } } - /** The level folder: `level-name` from server.properties, like the game itself reads it. */ - private static Path worldFolder() { - Properties props = new Properties(); - try (var in = java.nio.file.Files.newBufferedReader(Path.of("server.properties"))) { - props.load(in); - } catch (java.io.IOException ignored) { - // Fall back to the default level name. - } - return Path.of(props.getProperty("level-name", "world")); - } - public static CompletableFuture login(UUID uuid, String name, SocketAddress address) { Integration current = integration; if (current == null) return CompletableFuture.completedFuture(Integration.UNAVAILABLE); diff --git a/integrations/paper/src/main/java/net/scopenet/paper/ChatFormatter.java b/integrations/paper/src/main/java/net/scopenet/paper/ChatFormatter.java new file mode 100644 index 0000000..710cc90 --- /dev/null +++ b/integrations/paper/src/main/java/net/scopenet/paper/ChatFormatter.java @@ -0,0 +1,57 @@ +package net.scopenet.paper; + +import net.scopenet.api.ScopenetPlayer; +import net.scopenet.paper.compat.ScopenetApiImpl; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Formats player chat from the cached SCOPENET profile without a network call on the chat thread. */ +public final class ChatFormatter { + private static final Pattern MARKDOWN = Pattern.compile("(\\*\\*(.+?)\\*\\*|__(.+?)__|~~(.+?)~~|`(.+?)`|\\*(.+?)\\*)"); + private static final Pattern HEX = Pattern.compile("&#([0-9a-fA-F]{6})"); + private final ScopenetApiImpl profiles; + + public ChatFormatter(ScopenetApiImpl profiles) { this.profiles = profiles; } + + private static String clean(String value) { + return value == null ? "" : ChatColor.stripColor(value).replace('%', ' ').replace('[', ' ').replace(']', ' ').trim(); + } + + public String format(Player player) { + ScopenetPlayer p = profiles.peek(player.getUniqueId()).orElse(null); + String guild = p == null || p.guild() == null ? "" : "§3[" + clean(p.guild().tag()) + "] "; + String title = p == null || p.rankTitle() == null || p.rankTitle().isBlank() ? "Novice" : clean(p.rankTitle()); + String group = p == null || p.rankGroup() == null || p.rankGroup().isBlank() ? "Member" + : p.rankDisplay() == null || p.rankDisplay().isBlank() ? clean(p.rankGroup()) : clean(p.rankDisplay()); + String prefix = p == null ? "" : p.rankPrefix(); + String suffix = p == null ? "" : p.rankSuffix(); + String decorated = prefix == null || prefix.isBlank() ? "" : ChatColor.translateAlternateColorCodes('&', prefix.replace("%", "%%")) + " "; + String name = player.getDisplayName().replace("%", "%%"); + String tail = suffix == null || suffix.isBlank() ? "" : " " + ChatColor.translateAlternateColorCodes('&', suffix.replace("%", "%%")); + return guild + "§6[" + title + "] §b[" + group + "] §r" + decorated + name + tail + "§7: §f%2$s"; + } + + public String message(Player player, String raw) { + String input = player.hasPermission("scopenet.chat.color") ? ChatColor.translateAlternateColorCodes('&', raw) : ChatColor.stripColor(raw); + if (player.hasPermission("scopenet.chat.color")) { + Matcher hex = HEX.matcher(input); + StringBuffer colored = new StringBuffer(); + while (hex.find()) hex.appendReplacement(colored, Matcher.quoteReplacement(net.md_5.bungee.api.ChatColor.of('#' + hex.group(1)).toString())); + hex.appendTail(colored); + input = colored.toString(); + } + Matcher matcher = MARKDOWN.matcher(input); + StringBuffer out = new StringBuffer(); + while (matcher.find()) { + String code = matcher.group(2) != null ? "§l" : matcher.group(3) != null ? "§n" : matcher.group(4) != null ? "§m" : matcher.group(5) != null ? "§7" : "§o"; + String text = matcher.group(2) != null ? matcher.group(2) : matcher.group(3) != null ? matcher.group(3) + : matcher.group(4) != null ? matcher.group(4) : matcher.group(5) != null ? matcher.group(5) : matcher.group(6); + matcher.appendReplacement(out, Matcher.quoteReplacement(code + text + "§r§f")); + } + matcher.appendTail(out); + return out.toString(); + } +} diff --git a/integrations/paper/src/main/java/net/scopenet/paper/ScopenetPlugin.java b/integrations/paper/src/main/java/net/scopenet/paper/ScopenetPlugin.java index efd9c21..e071d21 100644 --- a/integrations/paper/src/main/java/net/scopenet/paper/ScopenetPlugin.java +++ b/integrations/paper/src/main/java/net/scopenet/paper/ScopenetPlugin.java @@ -27,7 +27,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { private ClientLinkBridge clientLink; private EssentialsHandler essentialsHandler; private GuildHandler guildHandler; - private boolean liveMapStarted; + private ChatFormatter chatFormatter; @Override public void onEnable() { // Install the deny-by-default listener even if configuration is invalid. @@ -51,17 +51,16 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { // Claim protection runs from a local copy of the panel's claims, refreshed in the background. integration.startClaimSync(); - // Central Live Map: mirror chunks and player positions to the panel. + // Live positions center the in-app BlueMap claim view on the player. if (getConfig().getBoolean("livemap.enabled", true)) { - // STARTUP plugins enable before Paper loads any worlds. WorldLoadEvent starts - // the map on a fresh boot; this also handles enabling after worlds exist. - for (World world : Bukkit.getWorlds()) startLiveMap(world); + integration.enableLiveMap(); Bukkit.getScheduler().runTaskTimer(this, this::snapshotPlayers, 40, 20); } // Developer API: other plugins use SCOPENET through ScopenetApiProvider / the services manager, // and receive level-ups, achievements and guild changes as ordinary Bukkit events. api = new ScopenetApiImpl(integration.client()); + chatFormatter = new ChatFormatter(api); ScopenetApiProvider.register(api); getServer().getServicesManager().register(ScopenetApi.class, api, this, org.bukkit.plugin.ServicePriority.Normal); // Optional client mod (HUD, claim overlay, shop and market windows). @@ -85,9 +84,16 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { EssentialsHandler essentials = new EssentialsHandler(this); essentialsHandler = essentials; getServer().getPluginManager().registerEvents(essentials, this); - String[] essCmds = {"spawn", "home", "sethome", "delhome", "back", "tpa", "tpaccept", "tpdeny", "rtp", "warp", "playtime"}; + String[] essCmds = {"spawn", "home", "sethome", "delhome", "back", "tpa", "tpaccept", "tpdeny", "rtp", "warp", "reqwarp", "warprequests", "warpapprove", "warpdeny", "setwarp", "delwarp", "playtime"}; for (String c : essCmds) bindCommand(c, essentials); } + HandHandler hand = new HandHandler(this); + getServer().getPluginManager().registerEvents(hand, this); + bindCommand("hand", hand); + bindCommand("handview", hand); + NicknameHandler nick = new NicknameHandler(this); + getServer().getPluginManager().registerEvents(nick, this); + bindCommand("nick", nick); // Register Economy commands & GUI { @@ -105,6 +111,7 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { bindCommand("guild", guilds); bindCommand("claim", guilds); bindCommand("unclaim", guilds); + bindCommand("autoclaim", guilds); } // Physical shops, actions from the map, and (when BlueMap is installed) claims, pins and the player panel on it. @@ -126,18 +133,6 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { } } - @EventHandler - public void worldLoaded(org.bukkit.event.world.WorldLoadEvent event) { - startLiveMap(event.getWorld()); - } - - private void startLiveMap(World world) { - if (liveMapStarted || integration == null || world.getEnvironment() != World.Environment.NORMAL - || !getConfig().getBoolean("livemap.enabled", true)) return; - integration.enableLiveMap(world.getWorldFolder().toPath()); - liveMapStarted = true; - } - private Settings readSettings() { return Settings.of( getConfig().getString("panel-url", ""), @@ -181,11 +176,6 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { integration.liveMapPlayers(list); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void worldSaved(org.bukkit.event.world.WorldSaveEvent event) { - if (integration != null) integration.liveMapSaved(); - } - public static String dimension(World world) { return world.getKey().toString(); } @@ -372,8 +362,12 @@ public final class ScopenetPlugin extends JavaPlugin implements Listener { if (claimed(event.getBlock())) event.setCancelled(true); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void chat(AsyncPlayerChatEvent event) { + if (integration != null && integration.settings().socialEnabled() && integration.settings().chatPrefixesEnabled() && chatFormatter != null) { + event.setFormat(chatFormatter.format(event.getPlayer())); + event.setMessage(chatFormatter.message(event.getPlayer(), event.getMessage())); + } add(event.getPlayer(), "messages"); } diff --git a/integrations/paper/src/main/java/net/scopenet/paper/commands/EssentialsHandler.java b/integrations/paper/src/main/java/net/scopenet/paper/commands/EssentialsHandler.java index b845155..9a01edc 100644 --- a/integrations/paper/src/main/java/net/scopenet/paper/commands/EssentialsHandler.java +++ b/integrations/paper/src/main/java/net/scopenet/paper/commands/EssentialsHandler.java @@ -27,8 +27,10 @@ public final class EssentialsHandler implements CommandExecutor, Listener { private final ScopenetPlugin plugin; private final File homesFile; private final File warpsFile; + private final File warpRequestsFile; private final YamlConfiguration homesConfig; private final YamlConfiguration warpsConfig; + private final YamlConfiguration warpRequestsConfig; private final Map> homes = new ConcurrentHashMap<>(); private final Map warps = new ConcurrentHashMap<>(); @@ -43,8 +45,10 @@ public final class EssentialsHandler implements CommandExecutor, Listener { this.plugin = plugin; this.homesFile = new File(plugin.getDataFolder(), "homes.yml"); this.warpsFile = new File(plugin.getDataFolder(), "warps.yml"); + this.warpRequestsFile = new File(plugin.getDataFolder(), "warp_requests.yml"); this.homesConfig = YamlConfiguration.loadConfiguration(homesFile); this.warpsConfig = YamlConfiguration.loadConfiguration(warpsFile); + this.warpRequestsConfig = YamlConfiguration.loadConfiguration(warpRequestsFile); loadHomes(); loadWarps(); } @@ -100,6 +104,11 @@ public final class EssentialsHandler implements CommandExecutor, Listener { } } + private void saveWarpRequests() { + try { warpRequestsConfig.save(warpRequestsFile); } + catch (IOException e) { plugin.getLogger().warning("Failed to save warp requests: " + e.getMessage()); } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onTeleport(PlayerTeleportEvent event) { if (event.getFrom() != null && event.getCause() != PlayerTeleportEvent.TeleportCause.UNKNOWN) { @@ -160,6 +169,12 @@ public final class EssentialsHandler implements CommandExecutor, Listener { case "tpdeny" -> handleTpDeny(player); case "rtp" -> handleRtp(player); case "warp" -> handleWarp(player, args); + case "reqwarp" -> requestWarp(player, args); + case "warprequests" -> listWarpRequests(player); + case "warpapprove" -> reviewWarp(player, args, true); + case "warpdeny" -> reviewWarp(player, args, false); + case "setwarp" -> setWarp(player, args); + case "delwarp" -> deleteWarp(player, args); case "playtime" -> handlePlaytime(player, args); default -> { return false; } } @@ -389,6 +404,88 @@ public final class EssentialsHandler implements CommandExecutor, Listener { player.sendMessage(ChatColor.GREEN + "Teleported to warp " + ChatColor.YELLOW + warpName + ChatColor.GREEN + "."); } + private String warpName(Player player, String[] args, String usage) { + if (args.length != 1 || !args[0].matches("[A-Za-z0-9_-]{2,24}")) { + player.sendMessage(ChatColor.RED + "Usage: " + usage + " (2–24 letters, numbers, _ or -)"); + return null; + } + return args[0].toLowerCase(Locale.ROOT); + } + + private void requestWarp(Player player, String[] args) { + String name = warpName(player, args, "/reqwarp "); + if (name == null) return; + if (warps.containsKey(name)) { player.sendMessage(ChatColor.RED + "That warp already exists."); return; } + if (warpRequestsConfig.contains(name) && !player.getUniqueId().toString().equals(warpRequestsConfig.getString(name + ".uuid"))) { + player.sendMessage(ChatColor.RED + "That warp name has already been requested."); return; + } + warpRequestsConfig.set(name + ".location", player.getLocation()); + warpRequestsConfig.set(name + ".uuid", player.getUniqueId().toString()); + warpRequestsConfig.set(name + ".requester", player.getName()); + warpRequestsConfig.set(name + ".created_at", System.currentTimeMillis()); + saveWarpRequests(); + player.sendMessage(ChatColor.GREEN + "Warp request sent. A server admin can approve it with /warpapprove " + name + "."); + Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission("scopenet.command.warp.manage")) + .forEach(p -> p.sendMessage(ChatColor.YELLOW + player.getName() + " requested warp '" + name + "'. Use /warprequests to review.")); + } + + private boolean warpAdmin(Player player) { + if (player.hasPermission("scopenet.command.warp.manage")) return true; + player.sendMessage(ChatColor.RED + "Only server admins can manage warps."); + return false; + } + + private void listWarpRequests(Player player) { + if (!warpAdmin(player)) return; + if (warpRequestsConfig.getKeys(false).isEmpty()) { player.sendMessage(ChatColor.YELLOW + "No pending warp requests."); return; } + player.sendMessage(ChatColor.GOLD + "Pending warp requests:"); + for (String name : warpRequestsConfig.getKeys(false)) { + Location at = warpRequestsConfig.getLocation(name + ".location"); + player.sendMessage(ChatColor.YELLOW + name + ChatColor.GRAY + " by " + warpRequestsConfig.getString(name + ".requester", "unknown") + + (at == null || at.getWorld() == null ? "" : " at " + at.getWorld().getName() + " " + at.getBlockX() + ", " + at.getBlockY() + ", " + at.getBlockZ())); + } + } + + private void reviewWarp(Player player, String[] args, boolean approve) { + if (!warpAdmin(player)) return; + String name = warpName(player, args, approve ? "/warpapprove " : "/warpdeny "); + if (name == null) return; + Location at = warpRequestsConfig.getLocation(name + ".location"); + if (at == null || at.getWorld() == null) { player.sendMessage(ChatColor.RED + "No pending request with a loaded world has that name."); return; } + if (approve) { + if (warps.containsKey(name)) { player.sendMessage(ChatColor.RED + "Warp already exists. Deny this request or choose another name."); return; } + warps.put(name, at); + saveWarps(); + } + String uuid = warpRequestsConfig.getString(name + ".uuid", ""); + warpRequestsConfig.set(name, null); + saveWarpRequests(); + player.sendMessage(ChatColor.GREEN + "Warp request " + (approve ? "approved" : "denied") + ": " + name); + try { + Player requester = Bukkit.getPlayer(UUID.fromString(uuid)); + if (requester != null) requester.sendMessage(ChatColor.YELLOW + "Your warp request '" + name + "' was " + (approve ? "approved" : "denied") + "."); + } catch (IllegalArgumentException ignored) {} + } + + private void setWarp(Player player, String[] args) { + if (!warpAdmin(player)) return; + String name = warpName(player, args, "/setwarp "); + if (name == null) return; + warps.put(name, player.getLocation()); + saveWarps(); + player.sendMessage(ChatColor.GREEN + "Warp set: " + name); + } + + private void deleteWarp(Player player, String[] args) { + if (!warpAdmin(player)) return; + String name = warpName(player, args, "/delwarp "); + if (name == null) return; + if (warps.remove(name) == null) { player.sendMessage(ChatColor.RED + "Warp not found."); return; } + warpsConfig.set(name, null); + saveWarps(); + player.sendMessage(ChatColor.GREEN + "Warp deleted: " + name); + } + private void openWarpPicker(Player player) { Inventory inv = Bukkit.createInventory(null, 27, GUI_WARPS_TITLE); int slot = 11; diff --git a/integrations/paper/src/main/java/net/scopenet/paper/commands/GuildHandler.java b/integrations/paper/src/main/java/net/scopenet/paper/commands/GuildHandler.java index 830a225..5fb4b6f 100644 --- a/integrations/paper/src/main/java/net/scopenet/paper/commands/GuildHandler.java +++ b/integrations/paper/src/main/java/net/scopenet/paper/commands/GuildHandler.java @@ -20,6 +20,8 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -34,6 +36,8 @@ public final class GuildHandler implements CommandExecutor, Listener { private final File guildHomesFile; private final YamlConfiguration guildHomesConfig; private final Map guildHomes = new ConcurrentHashMap<>(); + private final Set autoClaim = ConcurrentHashMap.newKeySet(); + private final Map lastAutoClaim = new ConcurrentHashMap<>(); private static final String GUI_GUILD_TITLE = ChatColor.DARK_AQUA + "Guild Dashboard"; @@ -86,6 +90,23 @@ public final class GuildHandler implements CommandExecutor, Listener { handleUnclaim(player); return true; } + if (cmd.equals("autoclaim")) { + if (!player.hasPermission("scopenet.command.claim")) { player.sendMessage(ChatColor.RED + "You cannot claim land."); return true; } + if (args.length != 1 || (!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off"))) { + player.sendMessage(ChatColor.YELLOW + "Usage: /autoclaim on|off"); return true; + } + if (args[0].equalsIgnoreCase("on")) { + autoClaim.add(player.getUniqueId()); + lastAutoClaim.remove(player.getUniqueId()); + player.sendMessage(ChatColor.GREEN + "Auto-claim is on. New chunks you walk into will be claimed for your guild."); + claimOnMove(player, player.getLocation()); + } else { + autoClaim.remove(player.getUniqueId()); + lastAutoClaim.remove(player.getUniqueId()); + player.sendMessage(ChatColor.YELLOW + "Auto-claim is off."); + } + return true; + } if (cmd.equals("guild") || cmd.equals("g")) { if (args.length == 0 || args[0].equalsIgnoreCase("info") || args[0].equalsIgnoreCase("gui")) { @@ -211,6 +232,39 @@ public final class GuildHandler implements CommandExecutor, Listener { }); } + @EventHandler + public void onMove(PlayerMoveEvent event) { + if (event.getTo() == null || !autoClaim.contains(event.getPlayer().getUniqueId())) return; + if (event.getFrom().getWorld() == event.getTo().getWorld() + && event.getFrom().getChunk().getX() == event.getTo().getChunk().getX() + && event.getFrom().getChunk().getZ() == event.getTo().getChunk().getZ()) return; + claimOnMove(event.getPlayer(), event.getTo()); + } + + @EventHandler public void onQuit(PlayerQuitEvent event) { + autoClaim.remove(event.getPlayer().getUniqueId()); + lastAutoClaim.remove(event.getPlayer().getUniqueId()); + } + + private void claimOnMove(Player player, Location location) { + if (!integration.settings().landClaimingEnabled()) return; + int x = location.getBlockX() >> 4, z = location.getBlockZ() >> 4; + String dim = ScopenetPlugin.dimension(location.getWorld()); + String key = dim + ':' + x + ':' + z; + if (key.equals(lastAutoClaim.put(player.getUniqueId(), key))) return; + if (integration.isClaimed(dim, x, z)) return; + UUID uuid = player.getUniqueId(); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + integration.client().claimChunk(dim, x, z, uuid); + player.sendMessage(ChatColor.GREEN + "Auto-claimed chunk [" + x + ", " + z + "]."); + } catch (Exception e) { + autoClaim.remove(uuid); + player.sendMessage(ChatColor.RED + "Auto-claim stopped: " + e.getMessage()); + } + }); + } + private void handleUnclaim(Player player) { Chunk chunk = player.getLocation().getChunk(); String dim = ScopenetPlugin.dimension(player.getWorld()); diff --git a/integrations/paper/src/main/java/net/scopenet/paper/commands/HandHandler.java b/integrations/paper/src/main/java/net/scopenet/paper/commands/HandHandler.java new file mode 100644 index 0000000..195268c --- /dev/null +++ b/integrations/paper/src/main/java/net/scopenet/paper/commands/HandHandler.java @@ -0,0 +1,71 @@ +package net.scopenet.paper.commands; + +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** Share a read-only snapshot of the held item through a clickable chat message. */ +public final class HandHandler implements CommandExecutor, Listener { + private record Shared(ItemStack item, long expires) {} + private final Map shares = new ConcurrentHashMap<>(); + private static final String TITLE = ChatColor.DARK_GRAY + "Shared item"; + + public HandHandler(Plugin plugin) { + Bukkit.getScheduler().runTaskTimer(plugin, () -> shares.entrySet().removeIf(e -> e.getValue().expires < System.currentTimeMillis()), 1200, 1200); + } + + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { sender.sendMessage("Only players can use this command."); return true; } + if (command.getName().equalsIgnoreCase("handview")) { + if (args.length != 1) return false; + Shared shared = shares.get(args[0]); + if (shared == null || shared.expires < System.currentTimeMillis()) { player.sendMessage(ChatColor.RED + "That item share has expired."); return true; } + Inventory view = Bukkit.createInventory(null, 9, TITLE); + view.setItem(4, shared.item.clone()); + player.openInventory(view); + return true; + } + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() == Material.AIR) { player.sendMessage(ChatColor.RED + "Hold an item first."); return true; } + String token = UUID.randomUUID().toString().substring(0, 8); + shares.put(token, new Shared(item.clone(), System.currentTimeMillis() + 300_000)); + String name = item.hasItemMeta() && item.getItemMeta().hasDisplayName() + ? item.getItemMeta().getDisplayName() : item.getType().name().toLowerCase().replace('_', ' '); + TextComponent link = new TextComponent("[" + name + " ×" + item.getAmount() + "]"); + link.setColor(net.md_5.bungee.api.ChatColor.AQUA); + link.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handview " + token)); + link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to inspect " + name + " (5 minutes)").create())); + TextComponent line = new TextComponent(player.getDisplayName() + ChatColor.WHITE + " is holding "); + line.addExtra(link); + for (Player target : Bukkit.getOnlinePlayers()) { + target.spigot().sendMessage(line); + } + return true; + } + + @EventHandler public void onInventoryClick(InventoryClickEvent event) { + if (TITLE.equals(event.getView().getTitle())) event.setCancelled(true); + } + @EventHandler public void onInventoryDrag(InventoryDragEvent event) { + if (TITLE.equals(event.getView().getTitle())) event.setCancelled(true); + } +} diff --git a/integrations/paper/src/main/java/net/scopenet/paper/commands/NicknameHandler.java b/integrations/paper/src/main/java/net/scopenet/paper/commands/NicknameHandler.java new file mode 100644 index 0000000..313d2c2 --- /dev/null +++ b/integrations/paper/src/main/java/net/scopenet/paper/commands/NicknameHandler.java @@ -0,0 +1,63 @@ +package net.scopenet.paper.commands; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; + +/** Player-owned chat nicknames, keyed by UUID and restored on every join. */ +public final class NicknameHandler implements CommandExecutor, Listener { + private final Plugin plugin; + private final File file; + private final YamlConfiguration config; + + public NicknameHandler(Plugin plugin) { + this.plugin = plugin; + this.file = new File(plugin.getDataFolder(), "nicknames.yml"); + this.config = YamlConfiguration.loadConfiguration(file); + } + + @EventHandler public void join(PlayerJoinEvent event) { + Player player = event.getPlayer(); + String saved = config.getString(player.getUniqueId().toString()); + if (saved != null && !saved.isBlank()) player.setDisplayName(ChatColor.translateAlternateColorCodes('&', saved)); + } + + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { sender.sendMessage("Only players can set a nickname."); return true; } + if (args.length != 1) { player.sendMessage(ChatColor.YELLOW + "Usage: /nick "); return true; } + String value = args[0]; + if (value.equalsIgnoreCase("off")) { + config.set(player.getUniqueId().toString(), null); + player.setDisplayName(player.getName()); + save(); + player.sendMessage(ChatColor.GREEN + "Nickname removed."); + return true; + } + String plain = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', value)); + if (plain.length() < 2 || plain.length() > 24 || !plain.matches("[A-Za-z0-9_ -]+")) { + player.sendMessage(ChatColor.RED + "Nicknames must be 2–24 letters, numbers, spaces, _ or -."); + return true; + } + if (!player.hasPermission("scopenet.chat.color")) value = plain; + config.set(player.getUniqueId().toString(), value); + player.setDisplayName(ChatColor.translateAlternateColorCodes('&', value)); + save(); + player.sendMessage(ChatColor.GREEN + "Nickname set to " + player.getDisplayName() + ChatColor.GREEN + "."); + return true; + } + + private void save() { + try { config.save(file); } + catch (IOException e) { plugin.getLogger().warning("Failed to save nicknames: " + e.getMessage()); } + } +} diff --git a/integrations/paper/src/main/java/net/scopenet/paper/compat/LuckPermsModule.java b/integrations/paper/src/main/java/net/scopenet/paper/compat/LuckPermsModule.java index b75c4f0..e0ae6f5 100644 --- a/integrations/paper/src/main/java/net/scopenet/paper/compat/LuckPermsModule.java +++ b/integrations/paper/src/main/java/net/scopenet/paper/compat/LuckPermsModule.java @@ -102,6 +102,7 @@ public final class LuckPermsModule implements IntegrationModule { JsonObject config = answer.has("config") && answer.get("config").isJsonObject() ? answer.getAsJsonObject("config") : new JsonObject(); if (config.has("mode")) lastMode = config.get("mode").getAsString(); if (context.getBoolean("luckperms", "apply-panel-groups", false) && config.has("assign")) apply(config.getAsJsonArray("assign")); + if (config.has("level_assign")) apply(config.getAsJsonArray("level_assign")); } catch (Exception e) { context.log().fine("LuckPerms report failed: " + e.getMessage()); } diff --git a/integrations/paper/src/main/java/net/scopenet/paper/compat/ScopenetApiImpl.java b/integrations/paper/src/main/java/net/scopenet/paper/compat/ScopenetApiImpl.java index 76adf87..6c4e65c 100644 --- a/integrations/paper/src/main/java/net/scopenet/paper/compat/ScopenetApiImpl.java +++ b/integrations/paper/src/main/java/net/scopenet/paper/compat/ScopenetApiImpl.java @@ -69,7 +69,7 @@ public final class ScopenetApiImpl implements ScopenetApi, AutoCloseable { guild == null ? null : new GuildSummary(str(guild, "id"), str(guild, "name"), str(guild, "tag"), str(guild, "role"), (int) num(guild, "claims")), dbl(j, "balance"), num(j, "playtime_secs"), num(j, "server_playtime_secs"), (int) num(j, "kills"), (int) num(j, "deaths"), (int) num(j, "friends"), (int) num(j, "achievements"), quests(j, "daily"), quests(j, "weekly"), - str(j, "joined"), str(rank, "group"), str(rank, "prefix")); + str(j, "joined"), str(rank, "group"), str(rank, "display"), str(rank, "prefix"), str(rank, "suffix")); } static ScopenetGuild guild(JsonObject j) { diff --git a/integrations/paper/src/main/resources/config.yml b/integrations/paper/src/main/resources/config.yml index 44f1f3d..d6db894 100644 --- a/integrations/paper/src/main/resources/config.yml +++ b/integrations/paper/src/main/resources/config.yml @@ -52,9 +52,8 @@ essentials: rtp-radius: 2500 livemap: - # Send world chunks and live player positions to the panel's central Live Map. - # Players open it from the launcher; you don't run Vantage on this server. - # It also needs "Live Map" switched on for this server in the panel. + # Send live player positions to the panel for the launcher BlueMap claim view. + # Configure BlueMap's public address and enable live positions in the panel. enabled: true economy: diff --git a/integrations/paper/src/main/resources/plugin.yml b/integrations/paper/src/main/resources/plugin.yml index 9b62606..3d1c682 100644 --- a/integrations/paper/src/main/resources/plugin.yml +++ b/integrations/paper/src/main/resources/plugin.yml @@ -50,10 +50,37 @@ commands: permission: scopenet.command.warp description: Teleport to a server warp or open the warps GUI aliases: [warps] + reqwarp: + permission: scopenet.command.reqwarp + description: Request a new public warp at your location + warprequests: + permission: scopenet.command.warp.manage + description: List pending warp requests + warpapprove: + permission: scopenet.command.warp.manage + description: Approve a pending warp request + warpdeny: + permission: scopenet.command.warp.manage + description: Deny a pending warp request + setwarp: + permission: scopenet.command.warp.manage + description: Set a public warp at your location + delwarp: + permission: scopenet.command.warp.manage + description: Delete a public warp playtime: permission: scopenet.command.playtime description: Check total and session playtime aliases: [ontime] + hand: + permission: scopenet.command.hand + description: Share your held item in chat + handview: + permission: scopenet.command.hand + description: Inspect a shared item + nick: + permission: scopenet.command.nick + description: Set or remove your chat nickname balance: permission: scopenet.command.balance description: Check your server economy balance @@ -94,6 +121,9 @@ commands: unclaim: permission: scopenet.command.unclaim description: Unclaim the current chunk + autoclaim: + permission: scopenet.command.claim + description: Claim chunks as you walk into them permissions: @@ -137,9 +167,21 @@ permissions: scopenet.command.warp: description: Use /warp default: true + scopenet.command.reqwarp: + description: Request a public warp + default: true + scopenet.command.warp.manage: + description: Review and manage public warps + default: op scopenet.command.playtime: description: Use /playtime default: true + scopenet.command.hand: + description: Share and inspect held items in chat + default: true + scopenet.command.nick: + description: Set or remove your chat nickname + default: true scopenet.command.balance: description: Use /balance default: true @@ -176,6 +218,9 @@ permissions: scopenet.command.unclaim: description: Use /unclaim default: true + scopenet.chat.color: + description: Use Minecraft color codes in chat + default: op scopenet.command.scopenet.help: description: Use /scopenet help default: true diff --git a/launcher/package-lock.json b/launcher/package-lock.json index 60789dc..cdd9e51 100644 --- a/launcher/package-lock.json +++ b/launcher/package-lock.json @@ -1,12 +1,12 @@ { "name": "scopenet-launcher", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scopenet-launcher", - "version": "0.4.0", + "version": "0.5.0", "dependencies": { "@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", @@ -15,29 +15,19 @@ "@fontsource-variable/space-grotesk": "^5.3.0", "@tauri-apps/api": "^2.12.0", "@tauri-apps/plugin-dialog": "^2.8.0", - "@tauri-apps/plugin-opener": "^2.6.0", - "@thoughts-on-things/vantage-mc": "^0.15.1", - "three": "^0.180.0" + "@tauri-apps/plugin-opener": "^2.6.0" }, "devDependencies": { "@lucide/svelte": "^1.48.0", "@sveltejs/vite-plugin-svelte": "^7.3.1", "@tauri-apps/cli": "^2.12.0", "@tsconfig/svelte": "^5.0.8", - "@types/three": "^0.180.0", "svelte": "^5.57.1", "svelte-check": "^4.7.6", "typescript": "~5.9.0", "vite": "^8.3.1" } }, - "node_modules/@dimforge/rapier3d-compat": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", - "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/@fontsource-variable/inter": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", @@ -700,25 +690,6 @@ "@tauri-apps/api": "^2.12.0" } }, - "node_modules/@thoughts-on-things/vantage-mc": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@thoughts-on-things/vantage-mc/-/vantage-mc-0.15.1.tgz", - "integrity": "sha512-muhg9950CgOa8keoCUV+utDHUKhkckwWGVn5O3HFMrgr7OrPo3VpEsBYVSUVL/RjmQDiqEaC19FPLGMTH97jpg==", - "license": "MIT", - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18", - "three": ">=0.160" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/@tsconfig/svelte": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", @@ -726,13 +697,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tweenjs/tween.js": { - "version": "23.1.3", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", - "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -740,43 +704,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/stats.js": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", - "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/three": { - "version": "0.180.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz", - "integrity": "sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@dimforge/rapier3d-compat": "~0.12.0", - "@tweenjs/tween.js": "~23.1.3", - "@types/stats.js": "*", - "@types/webxr": "*", - "@webgpu/types": "*", - "fflate": "~0.8.2", - "meshoptimizer": "~0.22.0" - } - }, - "node_modules/@types/webxr": { - "version": "0.5.24", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", - "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webgpu/types": { - "version": "0.1.74", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.74.tgz", - "integrity": "sha512-lgiI4hbuLcI9unnm2cL/tvCaQU45dp0xcLWJh5uB/9MBGvIA4F8XIk7nSiBeg+K4xWGYwgZKn80LmERI5CxoTA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -906,13 +833,6 @@ } } }, - "node_modules/fflate": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", - "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", - "dev": true, - "license": "MIT" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1216,13 +1136,6 @@ "@jridgewell/sourcemap-codec": "^1.6.0" } }, - "node_modules/meshoptimizer": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", - "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==", - "dev": true, - "license": "MIT" - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -1448,12 +1361,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/three": { - "version": "0.180.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", - "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", diff --git a/launcher/package.json b/launcher/package.json index 543a132..f34bd14 100644 --- a/launcher/package.json +++ b/launcher/package.json @@ -18,16 +18,13 @@ "@fontsource-variable/space-grotesk": "^5.3.0", "@tauri-apps/api": "^2.12.0", "@tauri-apps/plugin-dialog": "^2.8.0", - "@tauri-apps/plugin-opener": "^2.6.0", - "@thoughts-on-things/vantage-mc": "^0.15.1", - "three": "^0.180.0" + "@tauri-apps/plugin-opener": "^2.6.0" }, "devDependencies": { "@lucide/svelte": "^1.48.0", "@sveltejs/vite-plugin-svelte": "^7.3.1", "@tauri-apps/cli": "^2.12.0", "@tsconfig/svelte": "^5.0.8", - "@types/three": "^0.180.0", "svelte": "^5.57.1", "svelte-check": "^4.7.6", "typescript": "~5.9.0", diff --git a/launcher/src-tauri/src/commands.rs b/launcher/src-tauri/src/commands.rs index c3a6603..fada24a 100644 --- a/launcher/src-tauri/src/commands.rs +++ b/launcher/src-tauri/src/commands.rs @@ -150,6 +150,16 @@ pub fn save_instance_options(state: State<'_, AppState>, instance_id: String) -> Ok(options) } +#[tauri::command] +pub fn set_instance_fov(state: State<'_, AppState>, instance_id: String, fov: f64) -> Res<()> { + if !(-1.0..=1.0).contains(&fov) || !fov.is_finite() { + return Err("FOV must be between 30 and 110 degrees".into()); + } + state.settings.write().unwrap().instance_game_options.entry(instance_id).or_default() + .insert("fov".into(), format!("{fov:.3}")); + state.save_settings().map_err(aerr) +} + // ---- accounts ---- #[tauri::command] @@ -630,7 +640,7 @@ pub async fn get_public_servers(state: State<'_, AppState>) -> Res, } -/// Native transport for Vantage assets. Browser WebViews cannot fetch a -/// cross-origin Vantage live server, so the launcher uses its HTTP client. -/// Only paths under a map URL supplied by the panel can be fetched. +/// Native transport for BlueMap settings and 2D tiles. Only paths under the +/// BlueMap address supplied by the panel can be fetched. #[tauri::command] -pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option) -> Res { +pub async fn fetch_bluemap_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option) -> Res { let configured = state.map_urls.lock().unwrap().get(&server_id).cloned().ok_or("map not configured for this server")?; - let base = reqwest::Url::parse(&configured).map_err(err)?; + let base = reqwest::Url::parse(&format!("{}/", configured.trim_end_matches('/'))).map_err(err)?; let target = reqwest::Url::parse(&url).map_err(err)?; - let root = base.join(".").map_err(err)?; + let root = base.clone(); if !matches!(target.scheme(), "http" | "https") || target.origin() != base.origin() || !target.path().starts_with(root.path()) @@ -659,7 +668,7 @@ pub async fn fetch_vantage_resource(state: State<'_, AppState>, server_id: i64, || target.password().is_some() || target.fragment().is_some() { - return Err("map asset is outside the configured Vantage world".into()); + return Err("map asset is outside the configured BlueMap site".into()); } fetch_map_asset(target, etag, None).await } @@ -676,26 +685,6 @@ pub async fn get_livemap_info(state: State<'_, AppState>, server_id: i64) -> Res resp.json().await.map_err(err) } -/// Native transport for the panel-hosted live map. The session token is -/// attached here, so it never reaches page scripts, and only URLs under -/// this panel's `/api/livemap//` can be fetched. -#[tauri::command] -pub async fn fetch_livemap_resource(state: State<'_, AppState>, server_id: i64, url: String, etag: Option) -> Res { - let panel = state.panel_url().ok_or("no panel configured")?; - let token = accounts::panel_token(&state).ok_or("sign in to view the live map")?; - let base = reqwest::Url::parse(&format!("{}/api/livemap/{server_id}/", panel.trim_end_matches('/'))).map_err(err)?; - let target = reqwest::Url::parse(&url).map_err(err)?; - if target.origin() != base.origin() - || !target.path().starts_with(base.path()) - || target.username() != "" - || target.password().is_some() - || target.fragment().is_some() - { - return Err("map asset is outside this server's live map".into()); - } - fetch_map_asset(target, etag, Some(token)).await -} - async fn fetch_map_asset(target: reqwest::Url, etag: Option, bearer: Option) -> Res { use base64::Engine; if target.as_str().len() > 2048 { @@ -723,11 +712,11 @@ async fn fetch_map_asset(target: reqwest::Url, etag: Option, bearer: Opt return Ok(MapResource { status, data: None, etag }); } if response.content_length().is_some_and(|n| n > 64 * 1024 * 1024) { - return Err("Vantage asset exceeds 64 MiB".into()); + return Err("BlueMap asset exceeds 64 MiB".into()); } let bytes = response.bytes().await.map_err(err)?; if bytes.len() > 64 * 1024 * 1024 { - return Err("Vantage asset exceeds 64 MiB".into()); + return Err("BlueMap asset exceeds 64 MiB".into()); } Ok(MapResource { status, data: Some(base64::engine::general_purpose::STANDARD.encode(bytes)), etag }) } @@ -807,6 +796,71 @@ pub async fn get_my_guild(state: State<'_, AppState>, instance_id: Option String { + let body: serde_json::Value = resp.json().await.unwrap_or_default(); + body["error"].as_str().unwrap_or(fallback).to_string() +} + +#[tauri::command] +pub async fn request_guild_join(state: State<'_, AppState>, guild_id: String, message: String) -> Res<()> { + let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/requests")).await?; + let resp = req.json(&serde_json::json!({"message": message})).send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not request to join guild").await); } + Ok(()) +} + +#[tauri::command] +pub async fn get_guild_join_requests(state: State<'_, AppState>, guild_id: String) -> Res { + let resp = account_api(&state, reqwest::Method::GET, &format!("/guilds/{guild_id}/requests")).await?.send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not load guild requests").await); } + resp.json().await.map_err(err) +} + +#[tauri::command] +pub async fn respond_guild_join_request(state: State<'_, AppState>, guild_id: String, uuid: String, accept: bool) -> Res<()> { + let req = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/requests/{uuid}/respond")).await?; + let resp = req.json(&serde_json::json!({"accept": accept})).send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not review guild request").await); } + Ok(()) +} + +#[tauri::command] +pub async fn get_guild_roles(state: State<'_, AppState>, guild_id: String) -> Res { + let resp = account_api(&state, reqwest::Method::GET, &format!("/guilds/{guild_id}/roles")).await?.send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not load guild roles").await); } + resp.json().await.map_err(err) +} + +#[tauri::command] +pub async fn create_guild_role(state: State<'_, AppState>, guild_id: String, role: serde_json::Value) -> Res<()> { + let resp = account_api(&state, reqwest::Method::POST, &format!("/guilds/{guild_id}/roles")).await?.json(&role).send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not create guild role").await); } + Ok(()) +} + +#[tauri::command] +pub async fn assign_guild_role(state: State<'_, AppState>, guild_id: String, uuid: String, role: String) -> Res<()> { + let resp = account_api(&state, reqwest::Method::PUT, &format!("/guilds/{guild_id}/members/{uuid}/role")).await?.json(&serde_json::json!({"role":role})).send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not assign guild role").await); } + Ok(()) +} + +#[tauri::command] +pub async fn delete_guild_role(state: State<'_, AppState>, guild_id: String, role_id: i64) -> Res<()> { + let resp = account_api(&state, reqwest::Method::DELETE, &format!("/guilds/{guild_id}/roles/{role_id}")).await?.send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not delete guild role").await); } + Ok(()) +} + +#[tauri::command] +pub async fn update_guild(state: State<'_, AppState>, guild_id: String, description: String, motd: String, icon_url: String, banner_url: String) -> Res<()> { + let resp = account_api(&state, reqwest::Method::PUT, &format!("/guilds/{guild_id}")).await? + .json(&serde_json::json!({"description":description,"motd":motd,"icon_url":icon_url,"banner_url":banner_url})) + .send().await.map_err(err)?; + if !resp.status().is_success() { return Err(guild_response_error(resp, "could not update guild").await); } + Ok(()) +} + #[tauri::command] pub async fn create_guild( state: State<'_, AppState>, diff --git a/launcher/src-tauri/src/game.rs b/launcher/src-tauri/src/game.rs index d393c28..0304616 100644 --- a/launcher/src-tauri/src/game.rs +++ b/launcher/src-tauri/src/game.rs @@ -129,6 +129,9 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) - } else { current_options }; + if let Some(fov) = settings.instance_game_options.get(&instance_id).and_then(|o| o.get("fov")) { + saved_options.insert("fov".into(), fov.clone()); + } // Do not rewrite pack files or options while another copy is using this directory. if !already_running { sync::sync(&state.http, &panel, &game_dir, inst.revision, &manifest.files, spec.concurrency, deep, &report).await?; diff --git a/launcher/src-tauri/src/lib.rs b/launcher/src-tauri/src/lib.rs index 88bd91b..f0ab3c5 100644 --- a/launcher/src-tauri/src/lib.rs +++ b/launcher/src-tauri/src/lib.rs @@ -52,6 +52,7 @@ pub fn run() { commands::set_panel_url, commands::save_settings, commands::save_instance_options, + commands::set_instance_fov, commands::login_panel, commands::discord_sign_in_start, commands::discord_sign_in_poll, @@ -96,8 +97,7 @@ pub fn run() { commands::get_player_stats, commands::get_leaderboard, commands::get_public_servers, - commands::fetch_vantage_resource, - commands::fetch_livemap_resource, + commands::fetch_bluemap_resource, commands::get_livemap_info, commands::get_my_level, commands::get_my_quests, @@ -105,6 +105,14 @@ pub fn run() { commands::get_my_achievements, commands::get_guilds, commands::get_my_guild, + commands::request_guild_join, + commands::get_guild_join_requests, + commands::respond_guild_join_request, + commands::get_guild_roles, + commands::create_guild_role, + commands::assign_guild_role, + commands::delete_guild_role, + commands::update_guild, commands::create_guild, commands::get_guild_claims, commands::claim_guild_chunk, diff --git a/launcher/src-tauri/src/state.rs b/launcher/src-tauri/src/state.rs index 8a77960..a4a1f3e 100644 --- a/launcher/src-tauri/src/state.rs +++ b/launcher/src-tauri/src/state.rs @@ -41,7 +41,7 @@ pub struct AppState { pub accounts: RwLock, pub secrets: Secrets, pub manifest: RwLock>, - /// Vantage URLs received from the panel, keyed by linked game server. + /// BlueMap addresses received from the panel, keyed by linked game server. pub map_urls: Mutex>, /// Install/launch pipeline in progress (abortable). pub task: Mutex>, diff --git a/launcher/src/components/BlueMapTiles.svelte b/launcher/src/components/BlueMapTiles.svelte new file mode 100644 index 0000000..1f46adc --- /dev/null +++ b/launcher/src/components/BlueMapTiles.svelte @@ -0,0 +1,214 @@ + + +
{ if (e.key === 'Enter') onpaint([{ x: Math.floor(worldX / 16), z: Math.floor(worldZ / 16) }], 'claim'); }} + onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up} onwheel={wheel} oncontextmenu={(e) => e.preventDefault()}> + {#each visibleTiles as tile (tile.key)} + {#if images[tile.key]}{/if} + {/each} +
+ {#each visibleClaims as claim (claim.id)} +
+ {/each} + {#each paint as chunk (`${chunk.x}:${chunk.z}`)} +
+ {/each} + {#if playerHere}
{/if} + {#if loading}
Loading BlueMap tiles…
{/if} + {#if error}
{error}
{/if} +
Left click or drag: claim · Right click or drag: unclaim · Alt drag: pan · Wheel: zoom
+
{mapId} · {Math.floor(worldX)}, {Math.floor(worldZ)}
+
+ + diff --git a/launcher/src/components/ChunkMap.svelte b/launcher/src/components/ChunkMap.svelte index a47e595..9864184 100644 --- a/launcher/src/components/ChunkMap.svelte +++ b/launcher/src/components/ChunkMap.svelte @@ -1,524 +1,169 @@ -
+
-
+
{#if instanceServers.length > 1} - - {:else if instanceServers[0]} - {instanceServers[0].name} - {/if} -
+ + {:else if selectedServer} {selectedServer.name}{/if} +
{#each dimensions as dim} - {@const DimIcon = dim.icon} - + {@const Icon = dim.icon} + {/each}
- {#if central} Live map{/if}
- -
- Chunk {centerX}, {centerZ} -
- - - - - -
- +
+ {#if player}You: {Math.floor(player.x)}, {Math.floor(player.z)}{:else}Player position unavailable{/if} + +
- {#if !serverId} -
Link a game server to this instance in the panel before claiming territory.
- {/if} - - - {#if mapUrl && !mapFailed} - {#key `${serverId}:${mapUrl}`} - { selectedChunk = { x, z }; }} - ondimension={(value) => { if (dimension !== value) { dimension = value; claims = []; selectedChunk = null; } }} - ondimensions={(value) => { mapDimensions = value; }} - oncenterchange={(x, z) => { centerX = x; centerZ = z; }} - onerror={(message) => { toast(`Live map unavailable: ${message}. Showing chunk grid.`, 'error'); mapFailed = true; }} - /> + {#if selectedServer?.map_address} + {#key `${serverId}:${selectedServer.map_address}`} + { centerX = x; centerZ = z; }} /> {/key} {:else} -
-
- {#each Array.from({ length: radius * 2 + 1 }, (_, r) => centerZ - radius + r) as cz} - {#each Array.from({ length: radius * 2 + 1 }, (_, c) => centerX - radius + c) as cx} - {@const claim = getClaimAt(cx, cz)} - {@const isMyClaim = claim && myGuildId && claim.guild_id === myGuildId} - {@const isOtherClaim = claim && (!myGuildId || claim.guild_id !== myGuildId)} - {@const isSelected = selectedChunk && selectedChunk.x === cx && selectedChunk.z === cz} - {@const isCenter = cx === 0 && cz === 0} - - - {/each} - {/each} -
-
+
Configure this server's BlueMap address in the panel to show its 2D terrain and claim land.
{/if} - - - diff --git a/launcher/src/components/InstanceSettings.svelte b/launcher/src/components/InstanceSettings.svelte index 4f12b6f..0e1c346 100644 --- a/launcher/src/components/InstanceSettings.svelte +++ b/launcher/src/components/InstanceSettings.svelte @@ -71,6 +71,15 @@
Vanilla game settings Brightness, graphics, FOV, view distance, sound and other vanilla options are saved automatically when the game closes, then restored after instance updates. + {Object.keys(app.settings.instance_game_options?.[inst.id] ?? {}).length} saved options for this instance - {/each} - - {/if} - {#if info?.ready} {info.players}{/if} - - - -
- {#if url && !failed} - {#key `${serverId}:${slug}`} - (loaded = true)} - onerror={(m) => (failed = m)} - /> - {/key} - {#if !loaded}
Rendering terrain…
{/if} - {:else} -
- {#if failed} - -

Couldn't load the map

-

{failed}

- - {:else if error} - -

Live Map unavailable

-

{error}

- - {:else if info} - -

{info.enabled ? 'The map is being prepared' : 'Live Map is off'}

-

{info.message || 'Hang on…'}

-

This page checks again automatically.

- {:else} - - {/if} -
- {/if} -
-
- - diff --git a/launcher/src/components/VantageMap.svelte b/launcher/src/components/VantageMap.svelte deleted file mode 100644 index 250cba4..0000000 --- a/launcher/src/components/VantageMap.svelte +++ /dev/null @@ -1,208 +0,0 @@ - - -
- {#if !ready && !error}
Loading live terrain map…
{/if} - {#if error}
Map unavailable: {error}
{/if} -
- - diff --git a/launcher/src/lib/mock.ts b/launcher/src/lib/mock.ts index cf3a9d8..63e6144 100644 --- a/launcher/src/lib/mock.ts +++ b/launcher/src/lib/mock.ts @@ -137,9 +137,9 @@ export async function mockInvoke(cmd: string, args: Record): Promis await sleep(40); switch (cmd) { case 'get_public_servers': - return { servers: [{ id: 1, name: 'Survival SMP', instance_id: 'survival-smp', map_url: '', live_map: true, online: true, players_online: 42, players_max: 100 }] }; + return { servers: [{ id: 1, name: 'Survival SMP', instance_id: 'survival-smp', map_address: '', live_map: true, online: true, players_online: 42, players_max: 100 }] }; case 'get_livemap_info': - return { enabled: true, ready: false, message: 'The map generator isn\'t set up on the panel yet.', base: '/api/livemap/1', players: 0, + return { enabled: true, ready: true, message: '', me: null, players: 0, dimensions: [{ id: 'minecraft:overworld', slug: 'overworld', label: 'Overworld', available: true }, { id: 'minecraft:the_nether', slug: 'the_nether', label: 'The Nether', available: false }, { id: 'minecraft:the_end', slug: 'the_end', label: 'The End', available: false }] }; case 'get_player_stats': return { username: 'Notch', uuid: 'b50ad385-829d-3141-a216-7e7d7539ba7f', total: null, servers: [], events: [] }; diff --git a/launcher/src/lib/types.ts b/launcher/src/lib/types.ts index bc2f31f..ccaf705 100644 --- a/launcher/src/lib/types.ts +++ b/launcher/src/lib/types.ts @@ -222,6 +222,7 @@ export interface Guild { name: string; tag: string; description: string; + motd?: string; icon_url: string | null; banner_url: string | null; leader_uuid: string; @@ -234,7 +235,7 @@ export interface Guild { export interface GuildMember { uuid: string; name: string; - role: 'leader' | 'officer' | 'member'; + role: string; joined_at: string; online?: boolean; } diff --git a/launcher/src/pages/Commands.svelte b/launcher/src/pages/Commands.svelte index edbd6e8..6b84dbf 100644 --- a/launcher/src/pages/Commands.svelte +++ b/launcher/src/pages/Commands.svelte @@ -12,6 +12,10 @@ { usage: '/delhome ', does: 'Delete one of your homes.', perm: 'scopenet.command.delhome', aliases: ['rmhome'] }, { usage: '/back', does: 'Return to where you were, or where you died.', perm: 'scopenet.command.back', aliases: ['return'] }, { usage: '/warp [name]', does: 'Go to a server warp, or open the warps menu.', perm: 'scopenet.command.warp', aliases: ['warps'] }, + { usage: '/reqwarp ', does: 'Request a public warp at your current location.', perm: 'scopenet.command.reqwarp' }, + { usage: '/warprequests', does: 'Staff: list pending warp requests.', perm: 'scopenet.command.warp.manage' }, + { usage: '/warpapprove · /warpdeny ', does: 'Staff: approve or deny a warp request.', perm: 'scopenet.command.warp.manage' }, + { usage: '/setwarp · /delwarp ', does: 'Staff: create or delete public warps.', perm: 'scopenet.command.warp.manage' }, { usage: '/rtp', does: 'Jump to a random safe spot in the wild.', perm: 'scopenet.command.rtp', aliases: ['wild', 'randomtp'] }, { usage: '/tpa ', does: 'Ask to teleport to another player.', perm: 'scopenet.command.tpa' }, { usage: '/tpaccept', does: 'Accept a teleport request.', perm: 'scopenet.command.tpaccept', aliases: ['tpyes'] }, @@ -43,6 +47,7 @@ { usage: '/guild accept [tag] · /guild decline [tag]', does: 'Answer a guild invitation. You can also answer in the launcher.', perm: 'scopenet.command.guild.accept' }, { usage: '/claim', does: 'Claim the chunk you stand in for your guild.', perm: 'scopenet.command.claim' }, { usage: '/unclaim', does: 'Give up the chunk you stand in.', perm: 'scopenet.command.unclaim' }, + { usage: '/autoclaim on|off', does: 'Automatically claim new chunks as you walk.', perm: 'scopenet.command.claim' }, ] }, { title: 'Guild bank', blurb: 'Shared guild money. Officers and leaders can spend it.', cmds: [ { usage: '/guild bank', does: 'Show the bank balance and recent activity.', perm: 'scopenet.command.guild.bank' }, @@ -54,6 +59,8 @@ ] }, { title: 'Info & admin', blurb: 'Status and help.', cmds: [ { usage: '/playtime', does: 'Check your total and session playtime.', perm: 'scopenet.command.playtime', aliases: ['ontime'] }, + { usage: '/hand', does: 'Share your held item as an inspectable chat link.', perm: 'scopenet.command.hand' }, + { usage: '/nick ', does: 'Set or remove your chat nickname.', perm: 'scopenet.command.nick' }, { usage: '/scopenet help', does: 'List SCOPENET commands.', perm: 'scopenet.command.scopenet.help', aliases: ['sn'] }, { usage: '/scopenet status', does: 'Panel connection, features and plugin integrations (admins).', perm: 'scopenet.command.scopenet.status' }, { usage: '/scopenet reload', does: 'Reload the plugin config (admins).', perm: 'scopenet.command.scopenet.reload' }, diff --git a/launcher/src/pages/Guilds.svelte b/launcher/src/pages/Guilds.svelte index 9d2208f..3509c44 100644 --- a/launcher/src/pages/Guilds.svelte +++ b/launcher/src/pages/Guilds.svelte @@ -17,7 +17,16 @@ let allGuilds = $state([]); let members = $state([]); let posts = $state([]); - let activeTab = $state<'territory' | 'members' | 'feed' | 'wallet'>('territory'); + let activeTab = $state<'territory' | 'members' | 'feed' | 'wallet' | 'settings'>('territory'); + type GuildRole = { id: number; name: string; can_invite: boolean; can_kick: boolean; can_claim: boolean; can_post: boolean; can_manage: boolean }; + type JoinRequest = { uuid: string; name: string; message: string; created_at: string }; + let roles = $state([]); + let joinRequests = $state([]); + let newRoleName = $state(''); + let roleFlags = $state({ can_invite: false, can_kick: false, can_claim: false, can_post: true, can_manage: false }); + let guildEdit = $state({ description: '', motd: '', icon_url: '', banner_url: '' }); + const myRole = $derived(members.find((m) => m.uuid === activeAccount()?.uuid)?.role ?? ''); + const canManageDetails = $derived(myRole === 'leader' || myRole === 'officer' || !!roles.find((r) => r.name === myRole)?.can_manage); let loading = $state(false); let walletServers = $state>([]); @@ -58,9 +67,14 @@ ]); members = mList; posts = pList; + roles = await invoke('get_guild_roles', { guildId: mine.id }).catch(() => []); + joinRequests = await invoke('get_guild_join_requests', { guildId: mine.id }).catch(() => []); + guildEdit = { description: mine.description ?? '', motd: mine.motd ?? '', icon_url: mine.icon_url ?? '', banner_url: mine.banner_url ?? '' }; } else { members = []; posts = []; + roles = []; + joinRequests = []; } } catch (e: any) { toast(e?.message ?? 'Failed to load guild data', 'error'); @@ -85,6 +99,59 @@ } } + async function requestJoin(guild: Guild) { + try { + await invoke('request_guild_join', { guildId: guild.id, message: '' }); + toast(`Request sent to ${guild.name}`, 'ok'); + } catch (e: any) { toast(String(e), 'error'); } + } + + async function answerJoin(req: JoinRequest, accept: boolean) { + if (!myGuild) return; + try { + await invoke('respond_guild_join_request', { guildId: myGuild.id, uuid: req.uuid, accept }); + toast(accept ? `${req.name} joined the guild` : 'Request declined', 'ok'); + await loadInstanceGuildData(); + } catch (e: any) { toast(String(e), 'error'); } + } + + async function createRole() { + if (!myGuild || !newRoleName.trim()) return; + try { + await invoke('create_guild_role', { guildId: myGuild.id, role: { name: newRoleName.trim(), ...roleFlags } }); + newRoleName = ''; + roles = await invoke('get_guild_roles', { guildId: myGuild.id }); + toast('Guild role created', 'ok'); + } catch (e: any) { toast(String(e), 'error'); } + } + + async function assignRole(member: GuildMember, role: string) { + if (!myGuild) return; + try { + await invoke('assign_guild_role', { guildId: myGuild.id, uuid: member.uuid, role }); + member.role = role as GuildMember['role']; + toast('Guild role assigned', 'ok'); + } catch (e: any) { toast(String(e), 'error'); } + } + + async function deleteRole(role: GuildRole) { + if (!myGuild) return; + try { + await invoke('delete_guild_role', { guildId: myGuild.id, roleId: role.id }); + await loadInstanceGuildData(); + toast('Guild role deleted', 'ok'); + } catch (e: any) { toast(String(e), 'error'); } + } + + async function saveGuild() { + if (!myGuild) return; + try { + await invoke('update_guild', { guildId: myGuild.id, description: guildEdit.description, motd: guildEdit.motd, iconUrl: guildEdit.icon_url, bannerUrl: guildEdit.banner_url }); + await loadInstanceGuildData(); + toast('Guild details saved', 'ok'); + } catch (e: any) { toast(String(e), 'error'); } + } + onMount(async () => { loadInvites(); try { walletServers = (await invoke<{ servers: typeof walletServers }>('get_public_servers')).servers; } @@ -242,6 +309,7 @@ + {#if canManageDetails}{/if}
@@ -270,6 +338,13 @@ {:else if activeTab === 'members'}
+ {#if joinRequests.length} +

Join requests

+ {#each joinRequests as req} +
{req.name}{req.message}
+ {/each} +
+ {/if}
{#each members as m (m.uuid)}
Leader {:else if m.role === 'officer'} Officer - {:else} + {:else if m.role === 'member'} Member + {:else} + {m.role} {/if}
Joined {new Date(m.joined_at).toLocaleDateString()} + {#if myGuild.leader_uuid === activeAccount()?.uuid && m.uuid !== activeAccount()?.uuid} + + {/if}
{/each}
+ {:else if activeTab === 'settings'} +
+

Guild appearance

+ + + + + +
+ {#if myRole === 'leader'}
+

Guild roles

+ {#each roles as role} +
{role.name}{[role.can_invite && 'Invite', role.can_kick && 'Kick', role.can_claim && 'Claim', role.can_post && 'Post', role.can_manage && 'Manage'].filter(Boolean).join(', ') || 'No extra permissions'}
+ {/each} + +
+ +
{/if} {:else if activeTab === 'feed'}
@@ -407,6 +508,7 @@ {#if g.description}

{g.description}

{/if} +
{/each}
@@ -436,6 +538,10 @@ Banner Image URL (Optional) + {#snippet footer()} diff --git a/launcher/src/pages/Home.svelte b/launcher/src/pages/Home.svelte index 4f78dea..1547577 100644 --- a/launcher/src/pages/Home.svelte +++ b/launcher/src/pages/Home.svelte @@ -4,7 +4,6 @@ import ServerStatus from '../components/ServerStatus.svelte'; import NewsFeed from '../components/NewsFeed.svelte'; import Console from '../components/Console.svelte'; - import LiveMapModal from '../components/LiveMapModal.svelte'; import BlueMapModal from '../components/BlueMapModal.svelte'; import { abs, activeAccount, app, instances, selectedInstance } from '../lib/store.svelte'; import { loaderName, shortLoaderVersion } from '../lib/format'; @@ -14,7 +13,7 @@ const b = $derived(app.manifest!.branding); // Lucide ships no brand logos, so each platform gets a recognisable generic icon. const linkIcon: Record = { discord: MessageCircle, website: Globe, store: ShoppingBag, youtube: CirclePlay, twitch: Tv, github: Code, twitter: AtSign }; - // A game server linked to this instance with the panel's Live Map switched on. + // A game server linked to this instance with BlueMap configured. let mapServer = $state<{ id: number; name: string; map_address?: string } | null>(null); let mapOpen = $state(false); $effect(() => { @@ -23,8 +22,8 @@ mapOpen = false; if (!id) return; let alive = true; - invoke<{ servers: Array<{ id: number; name: string; instance_id: string; live_map?: boolean; map_address?: string }> }>('get_public_servers') - .then((r) => { if (alive) mapServer = r.servers.find((s) => s.instance_id === id && (s.map_address || s.live_map)) ?? null; }) + invoke<{ servers: Array<{ id: number; name: string; instance_id: string; map_address?: string }> }>('get_public_servers') + .then((r) => { if (alive) mapServer = r.servers.find((s) => s.instance_id === id && s.map_address) ?? null; }) .catch(() => {}); return () => { alive = false; }; }); @@ -58,7 +57,7 @@ - {#if mapServer}{/if} + {#if mapServer}{/if} {#if inst.server && b.features.server_status}{/if} @@ -83,7 +82,7 @@ {#if app.consoleOpen}{/if} - {#if mapOpen && mapServer?.map_address} (mapOpen = false)} />{:else if mapOpen && mapServer} (mapOpen = false)} />{/if} + {#if mapOpen && mapServer?.map_address} (mapOpen = false)} />{/if} diff --git a/panel/web/src/components/ServerForm.svelte b/panel/web/src/components/ServerForm.svelte index 14d1465..73a45b1 100644 --- a/panel/web/src/components/ServerForm.svelte +++ b/panel/web/src/components/ServerForm.svelte @@ -21,14 +21,10 @@ -
- Use an external Vantage map instead - -
Who can join
@@ -68,8 +64,5 @@ .opt.on :global(svg) { color: var(--accent-2); } .gpick { padding: 6px 12px; border-radius: 99px; font-size: 0.85rem; background: var(--bg-2); } .gpick.on { border-color: var(--c); background: color-mix(in srgb, var(--c) 18%, transparent); } - .adv { border: 1px dashed var(--line, rgba(255, 255, 255, 0.12)); border-radius: 10px; padding: 10px 12px; } - .adv summary { cursor: pointer; font-size: 0.85rem; color: var(--muted); } - .adv[open] summary { margin-bottom: 10px; } .dot { width: 10px; height: 10px; border-radius: 50%; } diff --git a/panel/web/src/lib/types.ts b/panel/web/src/lib/types.ts index f838fa1..36f0365 100644 --- a/panel/web/src/lib/types.ts +++ b/panel/web/src/lib/types.ts @@ -93,7 +93,6 @@ export interface AuthServerInfo { public_url: string; yggdrasil_url: string; pub export interface GameServer { instance_id: string; - map_url: string; live_map_enabled: boolean; map_address: string; economy_group: string; @@ -149,19 +148,14 @@ export interface LiveMapStatus { enabled: boolean; ready: boolean; message: string; - base: string; dimensions: { id: string; slug: string; label: string; available: boolean }[]; players: number; - generator: { installed: boolean; path: string | null }; - assets: { configured: boolean; path: string | null }; world: { level_dat: boolean; dimensions: { id: string; slug: string; regions: number; bytes: number }[] }; stats: { chunks_received: number; last_chunk_at: string | null; last_players_at: string | null }; - running: string[]; - last_error: string | null; roster: { uuid: string; name: string; dimension: string; x: number; y: number; z: number }[]; } -export type ServerDraft = { instance_id?: string; name: string; map_url?: string; live_map_enabled: boolean; economy_group?: string; map_address?: string; access: 'all' | 'members' | 'groups'; allowed_groups: string[]; require_launcher: boolean }; +export type ServerDraft = { instance_id?: string; name: string; live_map_enabled: boolean; economy_group?: string; map_address?: string; access: 'all' | 'members' | 'groups'; allowed_groups: string[]; require_launcher: boolean }; export interface HostedDownload { platform: 'windows' | 'mac' | 'linux'; @@ -216,6 +210,8 @@ export interface LevelReward { reward_value: string; description: string; icon?: string; + discord_role_id?: string; + luckperms_group?: string; } export interface UserLevelInfo { diff --git a/panel/web/src/pages/Dashboard.svelte b/panel/web/src/pages/Dashboard.svelte index 677fb4b..c3cb4d9 100644 --- a/panel/web/src/pages/Dashboard.svelte +++ b/panel/web/src/pages/Dashboard.svelte @@ -8,6 +8,16 @@ let stats = $state(null); let instances = $state([]); let branding = $state(null); + let showGettingStarted = $state(localStorage.getItem('scopenet_hide_getting_started') !== 'true'); + + function hideGettingStarted() { + showGettingStarted = false; + localStorage.setItem('scopenet_hide_getting_started', 'true'); + } + function showGuide() { + showGettingStarted = true; + localStorage.removeItem('scopenet_hide_getting_started'); + } $effect(() => { get('/api/admin/stats').then((s) => (stats = s)); @@ -38,6 +48,7 @@ {#if stats?.launcher_download_url} Launcher download {/if} + {#if !showGettingStarted}{/if}
@@ -69,8 +80,8 @@ {/if} -
-

Getting started

+ {#if showGettingStarted}
+

Getting started

{#each steps as s} @@ -80,7 +91,7 @@ {/each}
-
+
{/if} {#if stats?.live?.servers?.length}
diff --git a/panel/web/src/pages/LevelingAdmin.svelte b/panel/web/src/pages/LevelingAdmin.svelte index 35e52f5..a4cdf20 100644 --- a/panel/web/src/pages/LevelingAdmin.svelte +++ b/panel/web/src/pages/LevelingAdmin.svelte @@ -38,6 +38,8 @@ reward_value: x.reward_value ?? x.reward_name ?? '', description: x.description ?? '', icon: x.icon ?? '🎁', + discord_role_id: x.reward_data?.discord_role_id ?? '', + luckperms_group: x.reward_data?.luckperms_group ?? '', })).sort((a, b) => a.level - b.level); leaderboard = lb || []; const p = await get<{ settings: { level_base: number; level_exponent: number; max_level: number } }>('/api/admin/progression'); @@ -77,10 +79,10 @@ saving = true; try { if (draft.id) { - await put(`/api/admin/levels/rewards/${draft.id}`, draft); + await put(`/api/admin/levels/rewards/${draft.id}`, { ...draft, reward_data: { discord_role_id: draft.discord_role_id ?? '', luckperms_group: draft.luckperms_group ?? '' } }); toast('Reward updated'); } else { - await post('/api/admin/levels/rewards', draft); + await post('/api/admin/levels/rewards', { ...draft, reward_data: { discord_role_id: draft.discord_role_id ?? '', luckperms_group: draft.luckperms_group ?? '' } }); toast('Reward created'); } draftOpen = false; @@ -209,7 +211,7 @@
- +
@@ -236,6 +238,12 @@
+ {#if draft.reward_type === 'title'} +
+
+
+
+ {/if} - + diff --git a/panel/web/src/pages/Settings.svelte b/panel/web/src/pages/Settings.svelte index 75a1f51..588c1d1 100644 --- a/panel/web/src/pages/Settings.svelte +++ b/panel/web/src/pages/Settings.svelte @@ -25,7 +25,7 @@ async function saveDisc() { savingDisc = true; try { - disc = await put('/api/admin/discord', { role_sync: disc.role_sync, invite_url: disc.invite_url, webhook_url: discWebhook, notify_achievements: disc.notify_achievements, notify_guilds: disc.notify_guilds, notify_members: disc.notify_members }); + disc = await put('/api/admin/discord', { role_sync: disc.role_sync, base_role_id: disc.base_role_id, invite_url: disc.invite_url, webhook_url: discWebhook, notify_achievements: disc.notify_achievements, notify_guilds: disc.notify_guilds, notify_members: disc.notify_members }); discWebhook = ''; toast('Discord community settings saved'); loadDisc(); @@ -203,7 +203,7 @@ {#if disc}

Discord community

-

{disc.linked_accounts} linked account{disc.linked_accounts === 1 ? '' : 's'}. Role sync needs the bot token and server ID above{disc.bot_ready ? '' : ' (not saved yet)'}. Sync only ever adds: it never removes a role or group. Map roles to groups under Players → Groups.

+

{disc.linked_accounts} linked account{disc.linked_accounts === 1 ? '' : 's'}. Role sync needs the bot token and server ID above{disc.bot_ready ? '' : ' (not saved yet)'}. Map group roles under Players → Groups and title roles under Leveling. The highest earned title role takes priority; obsolete title roles are removed.

+ diff --git a/release-artifacts/0.5.0/SHA256SUMS b/release-artifacts/0.5.0/SHA256SUMS index dd94b7e..697b964 100644 --- a/release-artifacts/0.5.0/SHA256SUMS +++ b/release-artifacts/0.5.0/SHA256SUMS @@ -5,5 +5,5 @@ a54a1478c0740eb4aef20797037738355e3482ac1f4eaaa9b408d16e2303b7ed scopenet-fabri d9a22725d0b2554f31e010a6f596a73c023b339490ef982b5d53e8f866a8c447 scopenet-forge-1.20.1-0.5.0.jar 49098a90919fdea02f4eec26e332cd6a51fc7bb398ba1d55f87528f52610e250 scopenet-forge-1.21.1-0.5.0.jar 31e5076947f33ad1ec0037d92736b28eb8a47e4be84a6bacbf1dfd11308f579a scopenet-forge-26.3-0.5.0.jar -4963fad47421c8c4c9ff96cfa44fd808a5ecda2da1c2b08b0c18c43eaef5cb1c scopenet-paper-0.5.0.jar +1f4428de07b3061862c5dac3c7214ae87f9525385533a8c244ab67ec31ee76da scopenet-paper-0.5.0.jar 3ade714b0869b9fbb0e6175280b69d5bb6531543ce8ce48c7f231f170081dd5f SCOPENET Launcher_0.5.0_x64-setup.exe diff --git a/release-artifacts/0.5.0/scopenet-paper-0.5.0.jar b/release-artifacts/0.5.0/scopenet-paper-0.5.0.jar index b2b226cacbd9a8ff30219723aee860a220d71c83..45771d6ce16c777dc18a371b5e47434eb1b0ef85 100644 GIT binary patch delta 84462 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 z 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

-}%C~e|s{@>f)!j(eJ+Qiku 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 H9JK!f({i=c delta 81631 zcmV(;K-<63s3xtCCa?$rmn#hk5VO|`cQ6W`@w?pa0RR971DBCM1QU~h4hEMF5D6BO zhdVF?pTq-Cv${KQ0RdZ+D?JB4*=&Xo5&{Geg4{U>A|Mh#APEu(0g?y?R9KQBS+m)VyBiLz zN9%oCk5^39TXhgOc5e9`*rq($~F`FxMpb&;>FkFWbP{d$JpAii5+7fVA_(T3SY8s!qmc^Eo zLAdfL7_GrkI*fs_494~|oKbCf zI313r?HdyHZ!xNk&HnCS4B3CbInYME6LgpelNjg`!{6Fy#A1O^8$z6jbfs`V5Q-UX z5q~TY4pk(ERcbJW!GyHqYLoZG-4Y#2p^SlsY^?Wp8VrUa6t!nTf2ggZF&4p9aoeUr zg$C1gIEF?$H)656m>Xfl! z7x2{eZph?7r4I98K5h#_tu~@9kw6!b#Go|8<~s&12?PyX=mc1(!6F?N6IGK3IFEr3 zp;}ZDXR39mfh7zEVe`sRFc30Ugj)@zZ|kUksSdTU%I8qW6-lo!dlB(fuv&u#9U7qtw<9@l<)+h(77|aC*E6~U5u-jF zs_za4QRki9HHTGB?c0=(W@e2JYvE)x9R8LTqYHQD_$ldeaiY1oJJh123}rdm-HE;V zz+fFMx1K@ul)8S6hx>Ai1NMSGm<*?q@jH#d&M5=XhraSNkd}XSwHe9k2g#{x$ZBrX z!4I1lyA)5tP7HkF4hs=3?Z z#Aa0T3exl{XLF?&wxdG}2DTWT{;p|;!mrf_XTi5LI9rEv;9Le3hqX5k@CQv+3*LyT-RXzyVNdkS7vPlEBCaEIj0rQ_JiJPGs z+{$38%W{7$;m$69%wBa-SD?1DD@gKoyAF52ooFLlf&p~>rdFoatyTVz5v)=pQCe?- zU1Y)TMwl7&(K*L7DEI1cAKZ_+?C%Por*os<9bts|AUveO!#eDSJ-x#NT+KOQRV2J6 z&`P@MO~gDH_Vq%v*BnHUqKQXz*b8x#87s02v>1O;JA#_%>X~~DLrK|>&My`WN9R_lgcqrn>($%6OETYSLa1lPTDot8HC`Iip=h7ZyCSrH9`)rT3(^dHJ-@G*lK z?x=S9L#Dk!JN9r3tyt^NvuLK!q+-{1BWn>-V_M6T68Cql9WI`CHztYE364M+l-cOif_=A zGRB}h6O(JAQPkCdKNz+6>%&x5AS;+unbZI{OVZ1-bfz&cW@tw51u;}iBavw^sPrMtzuoRMrga2jieMI*5zfRan@FU1nte8PbMuaik4R>z0`85`v zAr6e!*#tHb(K5__24$J)PFV(to{V!h`Eiq5kff=IWtSh(Ek)F^H5NuZ%MpL$Xgl3i z(47$sdf749-|3HZ7)a)^cpr{nL=*M0S;)dz#NUc*9fv_D-iD%nC4y0n9gk65CfrW! z4g_0m0_U(w5{P*Ww*4RK#$oWr6tM+5JAo~vB+-E#?p4N>oRlDs7b92HbT6yM+ZM{6 zybObX4fT_Lpzmf&bymxkk!pXE?^aRzCF!hcIvE}~QisO~OpsWe&Q`E`vk+xgVeCT7 zh?GpHDjWE%oS9g)3@fe#>;=!Gni$s>=2e> zD&-U2KDz5(sS$``9MdqOn$;8IGKqG7F6+?PWe7h>d(Ad7)wq8F+3X5-rN*w(+12bC z6KwGFh5F+T^A)F8h(l}dpVv+5|{x&}j zK+_(H1-DQ0v7LYHCXL;!vmdY@qT={fOg?nuzAKQx+(j6Q!|4qV6X(Ak~rF0vohD&h2?&K_b9n>eWvOTvFam9*z2Y+WE+VOF`>9`<96 zJ)*O{40Chc*+~IFc$Gia?x+TZuum1MsGU*~wqIvG>;Q_qS@W`sG{dL3&7htB34`n| zs$Z0MN6;H%nhriVi#@KhC)ktLlOL=4KgTVM8;CvdvZu(IhdT_6hn_~l6Lu+4n)5Qu zfRD0U+k$^#j2Ozdb(ME^2V(&hZW2thQ z{xgoY+FIKEvGT5nu{p5Kz^Lc14DyrqC^_RbwB~=75wh!2Hf;?MAVgdKLRO$BamEOE?lC|BdD>5Q`S4lwp1BJ@!uu+nPzR|E;qR*+-}ZCJKE+ zdslztDS`MTVWpKtToj4;w^M!9$36x@&i;Qo`;>i#DT{if(_+~1b} zF6kkahM{sG6Z5c}1(Di%f2HkezvDq>*%5(rtrU9S1WJP<->fG(IpWvYA) zXqgG-wF7GH9hVK*TD^0R9ClhZ4Ju(@kfjd@g^6)$Ygc$vvmF~ezqy6 zi&|ST)pTaHWKnBFb`Q=ym!GHcZ|nR!WDt|)Rhn)Nd4BZ{E3p%+-q`M0< z2|IN0@$d5+HNHdVJNbW2DW_pDRWc|98DxP<{7!wIYPMF=GC$Dy4@sf20|_`Jz_;l9 zR+3)d^4hB98&)fRv_vR2E#a_-sJ$(0EN@doMxl^?9%z&{2qit zCABT;(cAd+0o5XF5K?m=zhC1I==?$cQ0h6H6tql?mW6Hg&sKkjzsyQXFVB+d0DOEm z-=py#>--VEH&dBok&U>dRG6Mx_OAkH>i9mQV1K{JukH@S6!JOz06(blpXmHiI>Iw3 z<(Qm`NINlrBE$Jpoj=Z>z^2W(G*$Ks`Wp2$WUzmx^QZXF5%V*;NlT8(;1CJp-TWDy z|AN@z!IT?eVatCnDMENInUo!#;x99!fnVwT1vP{H@M^$A6Er+Wpa0Mx-+kjjBM}wQRO7hyRg+T+EGzIVe}P zjf%zeO^Ezi=YQdt5M{TbDu>MFCb^w9=nDbY=dWS#2+M!y?hOp|U>Onomd@Yi@6bch zcB7@Es=Yg8J{#(@Ky7Vbcbrxf)jCzQqU{pi+adPT_XQIVaoW<-8m-wg7 z|3&jPWe2G3L;jJ*Ki2st{6DD&^tPkF@5ER?VUlo(6@g$dV4huDMl$?Adg#=o@y}9% z<+?D2Bm93;{)NuJq(!rX*s#VQ>^3Z$FW#f>L%INgIf=G~x>jx`*v!EExVA&By{BDxV z8bpyUMpC$lQD>-i1$ulxJyy>VqjhnV7=!kOPVLemi47SHN1eN39iAK`&|+PT6PRGh zm_dgVmu3Pv@RgPQ5fgMVQA{Go7_BnJK3S?t)kaKr#1u5#Vk+)sAH(gSZ!vn^4rRA- zf+&C0MVTl!^EB1XU}UQIak7TUtqpgT{m@&R~(0+)ST#sO%bY)5faCvaTjwLRAd6`;@r^KiFvx1FBX{R zgqyZ^8GULQm>wIE0Ob;5p;)Ae#k!~>(Jp^*8AE5ql&lXCHS~1gNpwBpL|rT;*OO&F zj`N9Sv@y&3*%(B-#jrMJnJA^NW4&T!lKd_j(3%hVqQ)m0M588}ba4{xUTJ1et)e%? z#R@eBQ-&A}v~(D)rYph4*6QM9(QJkxi(1SQ&p5XP#6;K}f;MQqI7Jhu>f$u90o{Le z#?fee8){En+O(mnj^eq+bt|iuQ`nms1R}@pCk&7Ei56lbO2)LzC6wEJ-JYvr;Bfk@2Der6c^TR zR!8zOP9r;U0a0}!gDIJG$VrY*Tr4ip#HG5pj2O|EpHAIFlEa~zZ87vGj(~q$^+gHk zD|B(CzAUOA?AITL1aJ=)DH`{ewi$;6W43v23>rQ z_&hufnv~mwwxc0yX(v{p;OroN?R3+xKIk+UG25c#L!r2t$o&C>=>HM9&h`7Fmk>wn zZxOd@;x=8}PI}%qcX8?=I;?-Du(i)rhiD2XVrp?GhNR*y+TTo0>lOldk0{l|z5T$d zZNn5hA&~dbvJeuMSbD`Epm{-%*=+#s`BCXV1Uqqv#rG*rOGm+ikOv|8}zgX1v=1D8)}KD=VX@H<){~E%pf8khOrJ> z_cyxut@xe!c8$5AW`KWE>{<{=jzAUo14Sx-OnolfXB@&HXZnKpvo8K3{%XCBkwDQb z!#Q6}OZ4FSuhIIir{)=Ue?APSnB)cVmM-3=Coe=@y|GoLN!0$XF5aVd^@>}>*b)eL zN3G6(i1#(|fiC_j{^cg#fe>B7k?j?=i96-GEjALT*0zT&Z{B}5W-&Riv>V0FWJ9G_ ze5eXd)?$Rf#}xd0g3#Rp9v1}c(B!|wl>fOo*|f6=-e=cX4 zLX#jh1~W1%!J>cGEfm(1^jC*NrZM!&Y!nlJvIkd_Il9#4Ad>@*NtB(YaKJIG!ZEHg zM-JwTHJOJj>wmOQ=F0+24%KBLJ>KjmhHf%~MprnJP+yPo8n^_%-z!JZqdBXeRvoF! zQF64IR8{%Aqo~32GZgb(08B{yQqz&sV^GrMSlq26UL=19u2atp<X$RzQbd>BQ(XNRoQ<%&cWR2_&ImbJQA*7@Kixk`t%ay2DI zb?C$^+QQ+spi$8l#XxKseyLLfX6;rs>avN3=BR&DGv+(S=!)eUU9P2KU3MUOjBK=Q z)*&F*4XjM-dYs({)8r{Se^8!^@r}zf!nE#QlbEs=rG4=k5p4{#h5Y7Ilw(p~{+^cs zSykv;P>Fd{m3nn{a9to|)OUAoLiO~kBRTnWSRuH^kG>y&Ta7Z-PQ}Ra1Bjo|vS30G zGUtDJwwfox^840(8L$@W=)*{KJ;^gPbf7sJt?Hz=@CsbQD1=4_Ys>*z<{2Km>9QVH zj7-nrjE_w0Gbp&0VZDexd4R8+P~|W2?m#EfJ~QJHTld!th&#x>LI=V2qb=+kq`W0= zwb~v{h7jh~#Cbf5G)i2aIk_F%L#-gf1~z{d1ri0DPN}a6L<0zTQ79CSCEk{CsgwREC)s>xs zj1i_K%qR33J1@N)^gT19l*gy|qfTG+%WE1nP+lj;(klV>YjwqGSZlwvhFY>>Wqp5b z)5-?qB-({=#IS3Xg5F6&KKUcWXRl$OgP1UoV-U#d782s4`#+a~)_JQx(zz-UrZb8N zD?1#j4n$3!&pP~vpo(9`4h?X7&WliI9RAA4ucfsoF0E;7Qf7I8&p{PJ{Y6U2(F0i8 zeo8r@rIVx8=@HTo&nB}hJ|bN`fX;t`P~pDMYpfaT`?%{NdUQ>%A;_TLp zk~jS=h>S&(WKmy{IpD#j9d98W>jy%zIz0dgj=}VrwpD)abFitOO0@!1epoZ;#j0fW zm)?!R@zMGtzE~`Ybd=-obLN$ zWGaZJ+n>ynkTg1mj%mPve5`+6LRHYF9o`gGkvWnQs{%Ir$fjZ>5lxl>=k|v{nyRM4 z;J724OK)+hYM0xdW~>6H+tbXG5Yzt1bgNq_U^9L|+Ld4)_shFCxn>G*_W%|F{U&=>SwR+zz{Ces`K&OA6{Er!9U4E-*U zkHL(Pd{AER;s(a@)?*--S<^B2X$*HqS`70otb){H#WY$A!?JoK8e3sd5d_ngyv5;g zEQ%Z1wSr#vZjI*2FVcUU;$Jh^!`J4q<~;dLuRp7n{;(9z@qiC4tSiXD(ZaV5ixwW|BdzdHndn9&tyWxC>c$EU|E= zF8?n7fxl+cuj6$2u6)nyIUa>^F0O&X#~EJdqYxI8gP4y$PC$Rfilf1BY(DiZLW)`O z8y8($%!*kV{_FKrp~%^V&be%GmZyeRU!u$Z@V)qJsn35?>sh9Gmg}B6&k9VwlTw6k z14*c;Q*w^->EDd@27L#u==}A(Qy8sDrZ}7Rm?&od;g0UE%q8oTiRxM25p|j%GtZsc zx3|h43^pQHE0HBDeV$dG)taY4XF*RRg9&{`o7kH#J}*hI;l}^}Y_Nu}cTx2WB%!+0 zhC&$f&|7~UR4mDHyh)~c)@J(Cl8&MjdalH|?5xBY)IljvRy5Bk3?>eU37Y4$lymiN zLlKTS7>^8jS z)89PBwKh_l^yAu${_HBY(5eeS;BSDul6}BR4uXHyybrSXf^WAP-9m4ZAq0S7Aekmx z@lH3#OG9p^>X&Rwtfh@ZtbG7-UP&DCdSEEV2_-!+QULxg#?SaV1TeX|)a;(hU_X@S z&xpfJF$0_C#$myJsM-%F;*aIc`(fqU18@>R$pL5v*axTVg$?_mrKIE$*kJXxA$GnV z=+J*)Ew#qs44fLv-zMX5<_?&@AI|H63mB{@>w(J{?0`bNxthV_a9w>#c^q!ob?7~{ z;sNY~p>Xq1_@TAhLAa&aT=KSkaMxbA2Y=kNA09AK|1p~KebiF22YPzoryjrwPc%B_5ByWW+Jo?sHS_>{LR|W^z9jz(r0uJN44O;!F+Nw86v@5JWA90k zG&dzL=TVr|EDBh5W3wz^IgQPp0ye0zIjevTZfw>H*pS9%ZviW4Y|g``LcCzxaJ+wD zV-a4m3)m>U_zKujjdMLkp1D~?S&z-u#B6V&w@|wcMigZgdS}edE-A_`!VUTA0x#Qj z=-rZoY^=p7Hf}FFIw>lT;13hAYzwhT3b82)u@Z$?nL=!uLTtK;*bEb~nI>Yh8|TU* zN$5N_J4?*g3bn$l+hAmor%;E9hh8Je zC}8X2>=e2yjWhoaK-O;F1xQ@`0d_h{irp7V?K{KS7fJ2wcJ^)A0eN`Yo;rWzTh1Y8 zr}mxa?E7|V-v!RT3sd_pQGL5$ZXNaSVV4)M@8YQI5ILoFgTiB|Y#hSpa9tN;p^9(yP!m-wqzxE(3oc_#p7g(`5%n zI9I`yGKkh(A~sWGrwoCI-v~a{>qU%8WEk5d;xt2cp|zJ{EcTuuBWT%+#C!Y&83o2h z@^@uScB3aan_nQe$gRlH3dmOD1mZGVZj;-|n#*~nHJ4|iL0=CezlLixwn>A_zkrd2 zhuD0`)*wf=ye)9^$_}+ z)P9bw8q7NRh&wm@|5-L;=OJ{13_I|PUHUZ)$8V(Za~Ok;@>}w3bgblh7vXert=T20 zAUn_<_4c{vgBLDPax%;uX!%^atB~j5V&~#LdEfI?9PoX((ei)lSD;s?84J)N{b(nQ zCmZ)Pj4thAw=>wocHM>k-u9F`LGGpj*!uuO?cE(``%2IZ{)q4~b#NHaD=!!0US9%J z;8K_am%)6v+=1Hy8@B~YniIf7X#aRQIpme(?QfH}zfa!&F?su^N32BsbPHYhDp5}rtatE7tzghy4lB%?cuXItlf2} z+GHS~OAM5bP2GhIxf?yoJx~VsqW`!LHeyV@86JQjJP2pNZj|vo@DunkyomR|g-75I zuoqr~xMK87+=Q8Mt2|#`fE#fJw8{(7E_-mTd*pvbWLt@));8q9%kpA*2{QarOB~FW z%dCwV1Df}1$kX6Lv=Cpz5{)%z96X-IXhuGRb{uuNyuvaVt8g~4bFit-J{6$y1xe{J z&Bg)T)q@U+7;LNNU^E+7T7WU^&sFv#U)c{XuQKG-@*4Twb@E!srq>HCL7jdT*ooN@%TYL~Ki%S#XNH5i}p zwY?SU8KmSFh{UtVjptx2{7NBFVyZvXZ_gN-0c1dMD0LGq=a5auPhX1c_Adx|u6B=J2OyefRlh2O(yRG{(jM-0rf1sozZd5>?*dt0hO$w*hyMu0k>QoETM!J-auFWCK`^nu=gDdIp4)Ge}{RPfLG!5M0g)*`M?&L`H+Qy)lF)*nrz`| zLfyL=DIE+o;wTg&Q-Vi>C+84Mr~eyDj>bDt=`m*D*Vkgi069uM2PySrrh0$=09o>g z{Gr8?fbBvQ5605v5q=kvf3K1t^JW-{(!;&Q=tH1*WjuW1;yUQALjEXeD8~221RGcIdw365nK{Z)0bT5#IDgC(wp?WkusP13>_bI7K}Fo6;_2Rc z`zaK_XUO!=aWlR^NBpIOnvH)pH5-wdTPcc-z{i=2^}NdB zhT1aFz!DSudma!)Y`{4>&xT(-5rpNmk)M=R*opKhf~?Y z216kl>UnI4!>q2d*|N%J%PM)hyaP9H9;SeI%DXJHnhUZ3qn9}ve{Ugvkb-gIXp4du zaV?5q*6rv2UaW-AlDvQaa(JA7O1bvakUy@-6X%~BL-|+N-)siE*0^!|`8Pd+3s_sa zM`Z06dRbfy#!vnMQ3%i@MsP@O@koNis1PIQetKbmQ<8lQc-G2MO=(eCzg{OsyW@pX}ZEBp_+Ue3MiBJA#tW%c?>7hgiYwr z#bo@H#Kp87kX?T&<6_2c)mR{A$Hg2|Rp#5aLrk`82i*))Hzhry5+zogpzbXK4v1L;9MU)kX`G8R zRw9k_kj6@+aei7FA09A`yX78>#tzFrOhUX?qe;WdnmuCOT`-*J+eod7XfF}%?`2il zA{6PxFp*U`aGGS}GzoF~ane^!>>JenAc4>$a<7HZ21-rwI)>nH!jFNU_8t-BkVK@b zSA5D&grk4hQU?-aY$V1Y67eJwM`b`_pWJUDvBu&`At7-Fequ@3-MwMg;YO`+z;^0& zh3rYf9-0C60eR4ZU27E|h9KB2B?rV=WbV#IcipQ9!B#^aYjD7I#^e?9Csq-{9o(bx zF&A*>X9Bkd!L3aT?xz{SJuaWHx7XzC7zB4wkGOxFK}qQWaWw(Gmb{x{@%MYPcs*|K zDQUO&$$@R}&*W1sNZe>6aZ@T1KkSXfMnuA&7Kxt^42h@ZGZqpJ3C5F*{U|Q(u;A|M z4c9=%Z%)hjUnJq?Wq|vv{G}V*yOVJ5>kapGgxir8?sEf!`@H;>8{GSoa3AUoH;iz* z(!zgzVPJ4ylrOo#eK-mC$Gzdk5N>x`xGxV3?yu!57Tjt}$aI4HNP*agj-t0tZHF9o zrURsIgQVB6e&eQ7RvmzeN9AuVNKKZX8cmRT3dB$F@>pCvQ6Qf3#KqHMMqE7G8}d0Y zik<6#Jlcjl8X^BK6J+^&`3DQ~%T_cVOq74A5c01utfLG1x&z|ZRM8oYKVLChUx|y~ zTdi3I?9mlTm-%YKWxkqlnSY9lzm**jZvg#x6F={?UYO!5F|a~P&Id2M0LHKjVGg?p z8ra3qj@J%$i6hbv+SCP+x<6vrGY4kMSM5kY8>F|ekSaSF82+LBlSTHQDG$c$5=4LM z@Ac(Ru!bEluUr)g$_m84vGt?4_zzw_#m^Vd&|{cKcPaY6h>LHCF%ko>0}>f;ZH*&! zVpqTrb|s8sSHWVu*08H>LWUsHXIS-wB{oS*kfc9b(zzJKA(&<+t>PaVYXFZbgEt^) z_#Zn&jyu_zmC}iyFEB<-zwz1fFBX3XqL!B~H$$I1JNb#InssShdJ3c$FTNg`t4dQT zgqqRAbr{`TkHPv44mm8h$tp*({+g^-4+Ym$SB{W>vv9f+Lnm5dA%Y$9G*l86!wTeZ zycEUdQPfIBQCUpVh4*rtd6{5dCLfR`uKTeAhO?be!fwJmX$PFZZg!x%&_;iEA)@2#**dgge=SB?C5s7g_~IdD^s`?$m2_&kd>65J}xUU-<0zy$wY){u^zdQ z>eNre%91krt(ML%kTs(vJPRuq!dWbAO*tAaJRyFM-0Co_P3Uw&YLt+2{39;%A=E_JVR6jwav;yC%A@?X~EI;+;3 ziy6CINyRO^Htg=T%87pt6mxA9bJfFIrtU75{|1gLf5`XR+>mPg$db4`iOe4z2`Mc* zAWu%51?gRkDnmAM?&@-pS8BIi6grZLr;&Z`}>`6naK@>VbKR z)b}X$eU$nhtG>sn@A2w;qWYe!zNf11QuSS~)~fJK_Z(y0&9Lug;T?^cqrT^=?|JHb zf%;ykz870~RpTlPMy z)_YF%Y=ENw2ba+t1RAsWZZ%#FOE*izX(|8!9!&zhKewoE(NB(KbwwatGY?T86vh)F{QG9cuS z#cLuh{&+AF)-A(K8ZIp!EZiClZ))oB3wVS{BL!^6JN!*StQ7aRcSyr%la8V>K)xf? zxhWWC8dsBH=Z*I3a@}dRi^gIqz0qK#d~q-o@X$CK@1hANO{7UoV-GtA_P!|)U*wPb z7e_*^LV?`EqMCoyDQCN>h$&msrEEug$U{CV7Vec`lZEMBYkIiZ7>@$&*+nY_yE2o? zX-Z-i>B=;)_bSuSt|=?4$f#pYnyyD_sD<84I?hG2Osb&SOy!a2rt%G)n>PpJA{ZNk zn>wSK1?3yjxxCrm9$J_RW*Qud_*;9yDtjSORP!aL)xv)<*Q9xLJX4NF(Q0R!`jzIY zG--jJOCxTzdjMZ#Qk5u8wqUVT))D9ula?l-t7L?*XqibTPz?ksuss-y$Fw%B5&G4c zR8Py9T;Tf&fgNb^7Op8u5%N0Nz9sJ86fWdG=pJ@n1@=Z z)kOi5HWDa4uxF(eE2=d~vHtj~V7v`A*s8Dy(s3Df*e_L6K|+AjO*(_lglK~R34_U0bU36FT`|Xc zI@_dk=v>SZkJLoA1)>Z6u|RK;u_wu)^XUQ?U1-upba9#-gQ3ugj?Q>5glt*s3&|*u zmok6l^?<9kw*)$5o3iX>CVibQhpxcIM0aSl8C=*~&s_A4#0-gDjjxjaIdlbGWzyB6 z6HZH0MbNJ`={iB(9gifWez04zg@?D8d16W<3L|cEn&pjsHOZUMVX$EiFBrauC8r~s5v8i&a z6&$^j?lrwiqiykxSaoWW+yuT%3d0<$> z+8|_9)sJHX{|5b|O5rmYyvu z{>-GG(=RNUS{V#%6NN|{mtj@uhrz$1U%TjilYS%Gb@btfSfbbzjE4dei2Q%fq~FsY z5DhO`QN3)wu4J*z^=m3Q}x?kkc+!*(_Y{phTrcYe-FOxo% z=;NqEcbA~eACuSGK-@25KQn*nKXkwnuLS%MRBuOfd7>Yr_X|4c0%FYlQ$1~nh2)}O zOw_8*-yZO=!44N^nd}t%HzZ|>g>8OB4k3x3JTVZyL%+BgX_8Yy!U~_8Jxl~{uxauD z9tgpS1~79AlzLGrJ1|wW5sV6e__O%NXj4np zDK@!8)^Rph)zs9l@{)hSNQt&{In%Nuil&ap{|N=AnmkRMUk)96Wfxkp~B}S2balqD@>j(+cg@h7I~5BJ(R=qc&dw$lK9dBN}Wd|l+@2g ztQd$Q2H;AQ7w|&MgeEefV+s$KS4nh{o(s8IEKMMprI!W5{tbVy5*}W{OI=)T@-jZ* zuxVhQd965OK_n6i_$7?bz=1VIYrNz{48V0<@8abqpD5BkqJQiy^Q@H@($Z*h6R&_E z!muEAZVl-CtJ=-0AZ)P?|F*E3o1s0DEV_9u2Ez(kFDK)rH4w61BqQ1Y6Xj+Y2_xR& zcXNw~cZa`4VlIC-V`68#qcsrT;bu_Th{ikIjP%7JsiT`=+uUMQ;z8M04!4^e=7=_U za^TS@J7F4K*uO6pW1PcVOpY?lxyKqOhK6a%A%?BZ6jsShohEOUnFiQ92*hPFY1wY_ zj+B-~fl#95bd%4JUE8ZK06_$NmdR(!fNTrg)ZrKCxh8*~C!Gf-pp}|b(sY5z7fO?v zXj%liNyEh^Um^|mObzi4fqu>8%TkaQB@yuDCSM_7dx7OPc}4rKH2EsN+H#_+BGFK* zm6oJXNkeDn@U?uMi+7rQy@Zyzy*mJe88mki7R!13uSoyr;Zc0E$+z&WV7I>&eMb~7 zTC^y0km-LV7d_-+MDs(_j)Gbu(Lnj~NK7WX(#?0nq_zjcftIL$W4t1=H4xn<+Ynv8 z*W~-S3#4fUj^UVApNWNiQOe{eF!=)}KgbV39)Ztltptk%{D{eqCcw=Cc+BL-6M!`W z_?F2}@RL^Z)9l}-hc1?er%c{04MOAP!FWrXhi`x9y(VSx(?W{A+W>JEFGkkF&!%Pd zP0>I=vU+}o3@^F)1(RRom$0CvT3Wo*&j3?{%Z;63oidj%2uf<)*{;LZ^Z6B%U*&yJ z;f;}KmA|D;!j;1elXN4SU+{vn`%QkGzXKED@8}322_I9Kam~ecMX&f>lfTDr!m`64 zA)S8>RBx=@5Yy4-!hZAGex~ckLx11ow;fE&wNUWSA7CbE= zfcH%PF(WW`Y9|>J@0*GMylE(LM8Q9!Mi>7a3X-YaOQs}XIs7Y=f6ebh*+g)~9xRv8 z8NpAkY#~>h-^fW+4KZa zOlu1dlRzxh$HB9!4|6b+VdQKV|0_L2tHs(;IPh%#g#T^wXZ#-s6Jm>X3#t~uGSz=% z=sH{aes1y?($~G9W<`~B22hcySOaUSYm-33R1N`ptN<$kcACni!OK@PEU!uS&Nh`t zgXy{+Jrl|(#nHM%!)(KF|biG^U%8|bqIWG;OhrE<4alt4x#8g8?`X$O)5Q?;H zUMm}+Di6ye2SH5z|1qK=Xyg6it(4D59)AM|5lK1M?#})ydLY-8Yk>-7Okjaf7F8Rt)g{sJ< zCY!2QmGql@^@yT`KGwFz7gir`ayhr!ugXkSuBISV5R6l%T6{REKCJNSyxD)Prh)D$ zac8@Kd#OIxN93uFHPv)A!}3hkVrMf2kkv)iUh?vOFXgFO!sd$1Cv%zD4!fu3m};&* z)R#jzebn!zA^Z^xn3`{@O3AdG(Lg&~wU>s{HZSG#M_w8x;yGN30+G?btEDFGQ`Jmm zhdW^Ha{!}GFjb9MeRnWc9}a&ZIPlU4+9rpPA9`t|w$AEAQ#GhY2v<}rWGs#ljA`bf zEc2n_OtnI-bg5OQTCJMX5Bhh+w0sP<@B8HDtTq@51+8n1WgfLwo#av{BSllEB(4DT z$eM^8^5qpSOC_@ab+T0NrBOn*(K_u>Ev9Ofw8ycep|aUaN2!g%IE;S}|Icjrk)2}# zouH{s6)I$huvvXHn`MQ6e_ZdSF&1+p!rTt+3VWS;9fGx9@`-$m6}*p@Vw_0NczVdK z5M&Ka5qhK&rBQ!turuaTI}-fRYcwG6jkJ5zc8Q!OSerT1RA;HP5nD^(+R_$i?bP?F zh3VmMEYV`Sresg-YodQT*Hq`J^CgSvgd1Lwik7wH?CQRO=OIKYg|rQwVVyg|I$fwP za;b|=b&0wZCg(^tCnqSK;U<3!>C}m7(rw+=mfQESnbtcb`ZA?f_sdN6b)B?`L6bzo z3UjJFRJ zJ#!QkW*$;hZAWw4)F<86I6dZmM1CTTEW;e`#=28|;?ls4LMc3$EDe_N1x0 z)l>FafwW8WXvIFOOZj6M2t-|b=O!cxb+fh@wvQhxAnrfc_vQ||#RW(Sc@ol7kLTZ2SgD*>0Bu_-P zQL4TpWPXFG_K3&~oR2UZEfcXT=NCj=-!j$r)!W!wJc3M9`=E>`aWxq;Q#m(NKQPq~ zMMkn0RW_Wk&N9nWWQnQBYSoX^4_A8~$W(0)v~PK`uPDf)_GSm@2o)5w--o8t z(Yn{g#98`6n2CG(q+g?=&%8`yzl_AJJH!P48*z5a38O_sPbA!wJaF%IDtMUdLM;?o z0qc6K^h=F|R>RB6(c8*kEC`ZUhQpD#^$8Z!n7(uAVLdY9?uZ5>QN#<#pZe*%-0p$i zuG;##>ZbaC2FNbV1&mbk&dPvCV|0jw_4Ogp$UuR9t{FA@laIW+oE3)CuNLKKz3&8v zyol7RIy>w;)DithP|%N{j=X(`%gey`FS`lx406#33mge2PtiA~A(QcnVfG<<9AOT- z2KdMHWbh$}Ul81FNo;V`p;iY@A^(Oz2!!)TH$e=4^D?Xs^VHIgncOk^fETtPl=#|f zaU`lgO-cV&M2@jAEFzz)R2*JwGka9g+BvD>p%l=l9wNCD5tN*L{ zjdnk%X+TK(m+$1cfgEy%BIwV^rn5H$TJ_B!7@WU0V*n=;w{CYCg*uNp!jsA)H^8F6 z?sorwcH28S(34+kc^=pouj|ZaiS%gDuWYWj=IBp=4>?g+wI@i;lxOdzrDVZeF z-o6=`%gC26v7ps4jZ>>W&}oUlGlN*m^cS_ALMT^zFx(l}yG6UK6Y{rjX!Vaz-nopQ z&a~jaPWnI@-j3gC_`$SiZLnhQ;r#{e=oSQj8`Cyp-%5=?RzA__n_TzSOSxvtOPfHC zJ_h+#0@|ZCV36vXvx+{DTz~@8+O=ni(J=ao_{LStjzNtRp2GTCF0C7w*dwea1WwBe>C804aW4HG@3Uep6j)) zw(w=SMq97bzy7~6Tsg>ToQm|x*lZeq4aV|Z1KxLXrYtQp|1dftqdQDvnQ=m{5$$yW zm07xSc_;>U%D}CrQEe>AHI}#yXwP(Rj&%Ckg!o~L#4%qi5FhJNX;zo2opUIp!Q(Ffcav_v*;>?ASNVGMkK_x!?Ra!4+71NKDIW7b-&Iu(%L@uA*ArdI$K&GE*m>Tp&jUOg~&(}zt#u{ zCP?b9u+e-a>|W3KR-hNxvU4?mQxVrid>zri)?lPF<_p<8wa6pbM+3-F!>FPAwM@mS z#F1|Vq>tWqpE)pKr^x0C`5L6cS03;1`{r0;KhJI4fDn)^>#6q~w00cPhrSqk6O5oH z5EMij5|8RwEe70lpiLOXTi)*(rjTgMz{4uKq<9+^ro*C^?=y?ZR z0ZVcP;Yxh%z%r3l^+lq(K|XxYV5#^D1KT@-QBXV>_RW|QE5bZ?AvaBvVSh_Jh+6L@ zg}3)0w&JF?ghlXeYl|eBE%{3h(j@Z9(JZxfA~Mlp5)LMRWHJM-9u|@7v#vD=&5t61=Ei_O+R_HY0eOmoDysj|AhlDF-_xzkCVYMUiGHfB>k3mwRJAD34{$RKaBzZ+h z@~Uan8_V6sYfJ;RF2{Ve{xDQ4>NfT>4FIvDTY;E9qQ#)^c#JoU@4AfdnY7(_6U^^1 z<_I+-n*+8@x4yQ2UknM=msvNYT7A2tH8kzZQ~u6@dMNNq46WgeV^VnI((3?8#g8p%4tu1d*lBP>pKL^qvV)ZlBmHIi}y(#a%Ah}xXdH*HJ-D1!C zuSo6zd)|LdBjjsg)JMp-hIo!85JJ6=9>2lchm?gq5}iBwDdNdbldJhD%6^hOJ=NTv zYFi6mo`eNf;O?brW^6#`PwD|@y$c?stM`TakRDBaU9!GVK(M`2`=tQQP z;ytvA=|;L76-2!+(E8ev-L#?ZX=(%7r&c(mdr3EKzL8ei)rhf&Vv6+mimZYxdxn`( zv!aGROy|k66n@+266}a}1x4=>0?548{Tzlv|gI0ANd=>pXr3DWAog8&t2Rj}I z5+!thFWpafZZUE8lrAm4LGWIYn z)MT0mVw_7u)CcNAa-sJG^+(8u6LZAXpVXg0pbFZoJ^~9InEOoi7qkjtAF&9l{))PZ z9qC?>ONaU!AjgwC=Ku|G(dvU()9IodR9&=x-bF1gIv;iL_cKxlF|CnoBzDmypAmhg z)n#syOIxrBCnkMRsIi9-6sGegn&8b9wIzG#N~yJU(=~NMK zdiWHnS>r3R^cdlko@Tvfki23%Ch9u)M%Tg1q>m*qw|3F68dOJh)7_BquD$f2@beLW zVG+^QAo`s)i>84{L`xvYOChV(VA3*bp%Xy*8oGdL>0+uw{}VByo_5l5u&6<^=U6Ca z3xc<1^>@_DX@zEhfq6El{{u{+cb@u(`lnv;!4#&5{97YG#z?FGC!njaXAC(H&>&F# z3!3YqD>P$Pe@Zhz`C1pL}vN*dE5^_lt)7L<(`#D(gAfOc7|KF9iB1k^#$oYxqN*f0!-y{8rSo<`eS_+DZQ zsJtU=N$kUN03O}BgGSMols$}2?qRh0%*eu$2Q>d>X||Wfs!o^{y{g&-;dKGD5)1vX zuJlFPe* z=fC&R=S;6*T+iEXR(b5oE=NYb8SUDB^tXu>{ViUDx3w*eNdEzdfjItnWFP9SLnWCtRt|{Hc$LM`t`jz%Mqt8Bb zlN*|8ZzusgGuw*NZrfoC{9=U{e!1A32`*u2QhIc-j&ZH~p zEZD5GVd2i9m*_ltlg_6fzy|(=E~Jm?BF>|Wc`RMRlju^O19e?NmuvQKgg^`?!*Cnf z5QJ%TrQtDhz}wBVO1rEq%(+lIDktVT-D<^b3$($=5(!9%4r-r&8KO}`xze6*G|w^y zh-FP`o1P60&8dZaEM*UYpt)%O=QKmx%a7rw;t;+vtGZ6hp;ODD2;e|tkY#|3VG#Rl z2x4)umOZl=vR9GSMft_McoxXH7ifZ*a}_xr)|Q3m=kX#(H!r@4h81KbI@Q<|cRXCu z&9%Zlh*X73&2SfgxVpLFA&r<1Ma~;1gfGGKr@#L2lMC?NPrNsb=SRdZ2JqZXWey=j z7ft!+FY%1i%&VpULFXm^I33THlQy4zDxNz#JmX=8c<|Qd$13rDVF9Fkr53&?jT!DP z`lbL@Yrs7Y0o*2lH5zbDmH;jnz)2c#o>Ks42;dYA*y0j@z-9rg*MRkI0jv?g1`Vjs z)nk?lq*X)a=ITK+1hP>>igWd-@d9bnkb>N7fp`USs)o37Ju}=x-Ehr_nd}es@tcL; z0{m+6TP=9w7s78Fe&^tKsdnK+w@xm|>gEvkiVF0JJGyASU5$6q3cI?kik%p3z`N;2ei2UU4Z4}%rCa!Yx|RP#xA7-*J0GMwl$-8UgXu0citbjE=pI!> z_i0UPfM*(DYf_d7WphA&HzJ;;(aUlW`@x4=?=G&Vmck`XEQFb2R)rC|DFT433JnqNuVf?;{ z-!A;RpW;1_B{}BQ;~&#&o7P-L0oJhsl?f8`g2XN!MPQJ`2nS_|yc^cLj)aX7#z@<3 zZjyio&w{7_&;5^evueCxwy2OfBATcF-`2PLozYjElOhd8lo#}$pVM9XdeFtOZ-lp z?FW1L&xvV@#hs?CV7pOAV!gliai$G41SPyWocL(^Hcf;TpG&VJTYCfQ|6K%+--APZ z6SnFtxY@ViQs05Y`VrEIcj;Ps57hX7F+B%2_&og#qkj$+{e|YoQb^ayG*U;?qAnjK z)#3wNEuRxb@h8FKU2}2zcbVM-61SMQ5HGRFPAl``KwA>?paOrS;85LC#*~CBil_MCD15 zFGay#*ja$H5Mg28d1_4KZZ)=l!Ym!?xWK^$<{mZ4pbMPbb#QfY=^j<8tWeit_cD?{ z@9}H6rT3vmzd_3U2U>{CWg~rv0QirHp8fIAc7-VIX~COfV)w9Olz2#w4QTI)BgQ(8lVc@*!OwN+fr>!ae6br1r7NuMLL`$GO^5;ZVWfI&OX zqE2?w4iM#fb|(*NfLBssZ%8Wa4YtC1=_0MBl22|mN;HRN)5)5De?1DTA>c-q<_03_ zfkNajs7bsv2n=%WNRrb4IpxdXP)2gv*sig+YVh4Ea#qf^3|g-IM2+BoJRyeO40M(^HDe1M89 zqkDFOi!c@rZSBED$6YCVT%6bm7`t07k+T{Tyj#W){o!_hFEs5{wLPv>r|i1yR&_U~ zOje)jG}ZDR?DWwW`^bs3ZWtd;!+AW7;R#g26KOuuyh<*jMxIQqTnyna16Rv6#*?i* zAA+l6sMaV0%KpKMeclH(KgS;_id73dSe>Z33|hVuDX5N8I}^GpgM z)4q;prHH`JBtdp239?g@0g;;g^J-gl->_7d<+LR~O;KCqpsOGiq}JAz?4x`+s{;+d zzP7*+wfWlHXnM+E(dktwBlUZd{q0Fv+O5KtB^ZEzaLE#;3CV6->~6j1eZ0V78&a(- ziP$I;g`@Tqy@IKAn3PDb+3*nRp)0g{_%KbNP7KnE7-vTuPW1P=u>sk{8nj zyaWtcO80U#-G`+739hC0xQ-5RJ)69oNAZb0Hi?s`RXjSOuk10()t|wviS#dHiZK;T z?WFsEjcI6gBx0z~4e`#4M6 zZ=?~WRv(MfdsV!L#yXf*oqdd!_FvotgjUcvUP+U96&=T`X%?@cdAycR;FD+rpF%NS zm!i(`r1Zv<2F!00WFF1XWU$nEAgKd13uHKdK!qRyC~!%V0*^sc#N;na`t+@XvtNN% zpKGO$bx*79%}=S*5Zou!?Q|P~-RcY(RNxp@TD(`C1E{(n?by-M!VNT-TVSeMX)JG~ zX}k$eXKRYHS(e0OnT^L_nq?dVpDTg$G?i-{3oebNB4fHS1DGrMa|QP9KeC;wU(%O< z?8(L5>T7n2Ax<^7G?`;Si_)CvN(;?u~(r^_E`fLA>m z$ejaeKNpNU4~)A2-sVC?0T-np>Q5r-PnsKl(yP|%!!<#4zLisG)|sSoKBv+|_%Hu_ z$~H>xgShZ@P;`X+T5IZ`efdJO5(;yJ&#wW90Mdnt8b*A;HO;SoRxZoGdg#W z4*7$-)YU0BIqEv(=2i%wm5Bd!>_0s1JtYT71o}Fn&q_q}&XUm=7+m1g0e#oOv*6!* zMf39zUJzdjLVt}0@Ykt;FNZ6?f~NCTw3e@iqq~NJd>x&^JEr}-v&jc=xZ*ZCHD zn{Q1yduQ z-Q>%6)H-^ji)_tNU*%PaTN2~YEXsAdekcnIInGSqePP^YdvrCzJZE-lEmj8SQ-F_LA5IujP@ z^#sL{3wIq{QJUcVcl&Tc-(_)`Y`P~v$S z0#iAfUQBV=N9hxPJJPipti`%ktB(ZSlyB6@2~o;<9w5eOGO(k1W4XPfwe}V4kP>Zh z2Td+U0P<$DOh8E(69}m}8R>G(vVQY;4iBm=QO&=|0GA@5 z%tiK?OLcjfE-%;RH*|TGF0aw$b-KJ>mpAJ2W?kN@%iDE%r!Mc-<-NM>(&YoXd`Op% z=(WFTJcge=ynjoVPwMh1UGCB4)4F_Cm(T0+MH}arfxW!%)8)5~cXi}uz$cw=yyuiZ zwfUs+)_S*UYqix{t%~yhe&@Y?*$tZj{kK2-WZt}abA0b(W)h$KY2U*D zFh#q-0Rp5AG#!#486oWy&w5XPvELJDE?!W7ink$xkUZHJ@I|H~$ei3|Hn1aPt4?hZ zpTD>v7-(nOLaf~AW};SVgJB5A9+na_Jb|?i7!Gb5jL=~uj6%pe@>Pg{)Xe~z2skz;_eelo zU=1mCz)(1r#UdNwXE{fIK-wdt-%&!5yNn5*qeCv_A!zkP6NJ&dPGS*{2wi}nE6MQ% zi)Z`%UI!FGu?@!PFc!ukj5sV-2z{Epky)OIXLiuv$Yf0;O2$rEYKP-VK$WzvUc=&A z)ZXHEKt7CT>P;ZI%!waR*`;JPk&rLYY+^Y{hY~17=-Vav+@BhM;>SB+3QV=ZG##cx z8A5R|)LdNOwsx&A!ur_cYi={?-bB5NYdtOgnX%0X>HeUnv0GbNHz0E>k8)b288da5 z1?5DfE)^IkCDW!_hXt^Z z)RQjW<%73X7q}21|5U3d@MgabFSaz6i@;kQhE7Cw6Rw*-jIz zfLa@@)S-@{a30!)AXwuGS9>=`xbG?*PJ-1W!vShQ=pUUm#@Ld;dg5%bAs&oVgAR>A zqL4~BRR$YOrsd`&kfm5UN4pZV&9KG>J{?YhwQ=Z@IVIA6wYCvV6LSs)>Cl!0aP#af zIs_m{lp_xiYF9)|&*@He8>~afh+90XocpIjD};53Folv0zH!rf9X4>2otl<;{B2&Q zMY|5C!p{ikFfTGX0SyuWy^KemuEQocgLn}3toJf}-DIBp+gVZgsIg)}`NjrsD-)0n z$2mHj3+Ew!q$nOVH?uVu8i%5vT50`a<0}d9nQ(y)7s5sC&b*N-Z#e8}_I78!3AB?u z@@5?_hD#6zws_Wh*`s#1rfzs9kUWcM2%A&kX4r0nTXeXUk#)xz8s!xz zI#JIWZ$KIG)G$>{b~vB|Znwc5I@}365VDTMjgdr>@I?p$V#%@HeQWHX8nYXr`D?h_ z2KVTHa4*}75r-YYu7?d?xIEyg_j?u50u7!`*};%7>L~njhEU z33!rbTcaY5nLGu;u$;=CWQByu4o?wCZ2@zCy;OKchi7?`KE@#TO0}W1JviGsFCj4(~F|{SBCvSDHmJZ+*}cB6!~?cpL10 z@FBt5KtgVZKM^?_LS9dVi2X6)<@b{HvBReXQH!V54u2(*kTh}ZV28iaFr)SbHMe+M z>b;?`9dhmPCEG2p-*1P1(u6IZ&{{7|{x3G(o_0HYOO3w3dLOUy9o0PzY`X0515MND zZSea7UiNuE>hKdBL=ZtzvaFoC99(tC|E4);-)J|(b3L{!%y!Isw>)YC$kjyLEFr8O)BFsCY6_3zbo{ar< z%)|kNA~Br|ZlhNP6cu(HM1VDV1MS3m9IWFI97;i@Df5b}2xEE?`np6$mFGHtM#teX z`cuhL!R;e-9Len|M!U+SQZY-%V|itVxiU!>H;>jaCq{pWUmPFtmpmQw`HMLq+FYn( zQOtlUL%cC~V+=eT3tY!P%H~urpY$RxgidcztI-ZEDXjV^S$nDeq-LQu za;O(2%rO&g4wP2*Oy<&mqtnEgrp0Xmmqkcc`q+!RGr z$^Vj&S6N6r9i)>K^emm6-`UC{$L|=$>Qoa2AS%?F;p51K`OBtY zsNB=A24Q5*VTN($el=dG<3$|)YI7D;&T@hdo4NI39WUY5rgMEp`nmkmZ6k;eo$YT8v!lt7U>qE8X5SksObUR@f?sdX3_@EPpbF|~ebSI3! zCm6gZj}p8dmp(d7|p)>RZ^br-)1oQ-jE$B6%cN>17E@4@s{2dcBCW%Rm1Of`~$wt;dU#L zfYhPzi1KrD>QbI!IVGU@u8!{^WeyH9_zi1KAzzeGgu4d^y(FJ%o|!!3VsE(3A5rUl z$RK~z4bmPn>?ovm{4+V7x`nG8_z8Y$!@ubGSI%R9bB}m&va2e&^EUDnf79`E9&hvb z{gjI~vUU8sj{jimm^5c`d3m)RzoHf0`EEOY&6SOA?(>a~-*TUnS!IjouVyo9$M5)a zk=qG5EHb%xfCc48gj@c@4B>y*l28EiNB{*OxMBko3h+ZUl-_y+4ZP!fvJkqELL)fGZj2^Y^7$)wD}hT#7n8J8Z^y8xedLLnDLq~nBLADif_i+&;vp(u9x z^?bTSnJqnnq4wg6XlB17~y z_DGibyc^g*#f?j_D)*zo1H?d^7^Djq$KxY^4?o0knM-^Tzc&R^#SmQ#6~hqv%~@Kp zntm#0tzJ^Gq_Ui%{{)E=fjV3lZZU%5?JzlM4{_7)XFRnWPGso&C|zW+8{}=q#hwi{ z{$PaswaC`RXtjOb;17nq7DQxmM6NFKL_Tj#_ESyui4E+do6+wUPKMqyB?uojF&PgmmwUJa~LwMATouB4V0~lmAa@C zCmLJyhOl+XS&xS#@dk|5<|JLLW)ZOa!V3btbLNDx>{`aDz+N=yqEY#&<#Q{4mXvc# zlP;Q7Twk_mX_*s_6Fw&8DF|o&pSqv~jasXr%y$wKTbG-=`vodXD6~jxuu?6L!#8Arwm-Q>*7>Szinn-?Su&`*%7DfVv{(7 zH$A+5Z)-3_$~7n_@!XUUOl0_fCc$RAIEQQN;w4LaDLszRL-dg^#7|QeFH)T*jxI-iRR|28LmVBsS5U86-2^U>v}SU$WR= zK?zf$?@pM)`P@|I#?@+%c)i!pYOqxo+tjwCQt*mE#4fHQ>!Tv6@al$tU|S%<-F~Hu z8^n!<;FpCfgM6MNb8_d|#m(dtT09$98zYjy_?O0NNE!1q(ZQU%9pT*nP*N*;F)H)s z9d*<{9g~>EJ9KfE_%#_)W8>WNTnKeJhw;#J)j@q}!tlTMk)yd}Rwfug25*<>w29ri*dz8L zxDy0f|(!o&?X+z#lzweGNipD zZyZAKXg(LHINap9%a~Y?>EdywRH~xMjI!$L^2JUlBOX%3`Wsz;JY^m?Ev4*89XB~) z1}o%D@hlt2=lHxPQBF?MDPGXUiz>4Z`%a~Cv&6GZ$yapos(6hPB||V2yeB56(4q4t z+yXodri|z7y7(RA*{8g!d~q2?Ycnbr%$#o*lm|MqJxjcykdH^X)gc&}jB@dN#_tcj z0XJ`6qEip?M~b+AvL~pB)t&7vR_f`_o)8JTjcUN_3Dvi|J%nZvgYd2{-eVB#l@(Rx zt7p}g+r>F;d*xPHmt9bPR~(()nliG+>u%uh?)G3C&+O(?ViIk&#-}9k*$w8oT=!M}ig0YqD(1PKv5wh)Q>~{E_?s?1=ecE7*+RSc zJLz_|mqTNBCBfjX^0eB;m*lOpC)BJ7ZU{&5k65%d446CQY4n8LzJQw#*NWI7{8JZS zi+>SwSf^s-dG_Hf#bFJC8SEE|Z*}o+j&&XC>`plcyZB!1ZH4Ozd_J9>&4N9Li@7BK zH0Yo%K_VZ2ohVT?!`{Y2!ruSX*Y^??IiW)MoG?!TB9nBPEGfg&O$M3U3p0*zd!g5L z=JSSCrLj(`E_GhV(Y1*o#qTa%4(4~>_p9ZGp*rq=mIJwVj4p>uw{ef8*0Vv4nav+Y z>T(o+U|+h>7in1IkQtKixXEmU!3n4_Yu2pUvvZf(We$O69L5(LXRO5`Z@4uWXk@hW zbeS&;2$a@fIMT!RR4+8_mB2iIkuHllFv$pUCLfL{gcIY z&4hb@2Mbx)7g5L}$CDk5rw<>H+2uraJ(K;t6BaY0YcPr8WjR@wQ@VC>jG%c5Y_`ei z@!52@>kQ=^y$x-om^L|s(uYGf2`YWz2ni0k%#<2mbHEd63waSH#-9XC?tS0Z3LdAH zB5eG>z4sX1UCE`iM>CF~`&CkOS>48mv`aXDLgEbqg3!OrLmrOa%|KanGuKH{m%DgW98HZoTr`T02{WxrRWbLcO4aqBSisv` zqrgv1G}+OxDJ2-N?z=R_8U2LUe@c7lafC6$O0pL{hqin*t*xHyT&s0?g!G`%8}aym zD1OwMh^6!%Ht!;hJUqa>bs9x*Xm8MS=HRLK@+zJXUm=nWExb)sA8c&rp}kldV&2NQ zi<`59A$2z}{uUpfrO~uOzAspEc+E;^&lS@qcc1p?h9@-`PRx1ep$mc6$XAg><3>WN zf{14vXRokoD^+SA-5`)5zG9w#6x-;3JSIqv#nt0>Ij?>yyM#(svg8I55MD|&Y~+Hz zB(G7aDBl4JH}er!Abw*L;kfuz`taMJ%5EfijUCZ3M?07UZGVtPx+oiq_*y773;Rev z%L0L5Bzlc}_@T3^(Op($px@I{-{{GTyIztt_Bi4Mu_UR5g8)vxjkKB7fF^H$qp1Z; z?XX@q=n#hVl5AR*Cw`xU+WW`B#KN65c3k3Sit)xB^%lmm?r59WaI(gZA+H_xh41ZT zO;X!)N3(@aDeb@;)z6f>vve(yz!UNvL>p1U5=M&;5-`G;@LRUeD8JO1cY_} z*Bb$h9A5(=`K0vta4|b!R&OSM#y$15Qa78txyQUW+Nrx7sC!sh6wqYGGfu_uStqEQ z^8bHFtAy`>xVg>jD(m>JYxw{1vGTI+_$an#9mn@64c&8@pSxT7EK-R{-LFj?p%+#> zB>f!{w{+|ctt`^4lAQnaDhN*>>dq2l`m8Z3VS2%@Fzlli)&`lTm-M-8Pa4PN8- zFw$cqs3IOphI>XZ7zsy0p4KY9!r2(M%iW}7v&q$a8#yWF-9#hTajV}BDRHA+?nTJO z1p$A%+lY?C?gmf5?N#Azw0AUaE3(V|ggUCl!zyj3tW#YHrcV!%Ex}q!=Np4=GSu!h zyj)k@q0;iO*IjJP$v0eoY0x7GgL=3FOTaybP;lf^BF*;%(agAz%Hw}SS^QCqS2H|K zdWuW=I(A7Htju2bWP?Ie(;&HDl6=kqsq%SU-Yf5;w-@>CC0+hn-km02>2}Gj=U-r0 z)-PGU#zTLr%RA+cH2FKe(Rjq7ZkKP8?ZGKirnqZnE?8JzUB1MBt(fCB!?3WMzS-sP znKcG7=q_|GAac7otO$7;B0eICF~+jJ?m0^%z8W3JvriOp2`y$ zrpWDa2fP~$S+UD^SfI>dYL9|DDpTfG$#!%*#_awc1qf*DIiWiz7>d$dwdC>|Y9l9Z z6UQz;Kp2n3<|aaa+&Ht1M@F}bx{duvt!VUap|FyA@;*;=o5#<4Kyf=fd^KABnZh88 z8ss|NlPOwKMBvES-D2F}XNfXrHt$QzPY{le4m9spyNjdH@*B@#FkpkHy){UDao6*8 zc=;EE3DJ@K>t^n{MlU7yzQ}1Ce33PcAH|q9J*UQ?S>#(Nbn2`sntWOy2cxd4*HUO5hm6| z9%0yCTEl36bLSW*E3_)H#XMOF=i22zW2P}~q`T{A`Bj92c`4m^=Q#~W5rx}WRq32v z{)^leDw*WXN@EYR4h!74s;H=l48gao&HvWr?Xtrzzb8M0rWhvfet^6GsLR{9`$0;< zx|yB?if7l5j3&+|nyC%bsPU>Z=hP(4scE{rMc!(E*OJLuAgiC%)#zmBPAR(F4o}*( zREh(zGU$n!PuXYHvk&*|tIMmnXBwp^Y;TNzZtU7{CLit2Va#&TZFR6~PV!vncDt+H zIfW~8?OK2Hy~drB&+h}s8Drf-B0Jk@H|D$9V24a$cH>4jE>O}CZ76c%a+TQxNagI> zAaZek-Dzg1r=gKk34_6m!4O^kQeHuC!}#qOU0x1P(wm##M(FZV^)`y%vUGVKx>h*Rd%g_k*-_N)MUOFA@pH6 zBg;iYa>);C6gv#Y@=%DBu*n-~Si_t!Z3z2+X^ldn$p&Ri%Rzd@FojvgV!XP$-5VL< zUfSi=OqLQB;w9t;?C9OHhHaG zo2L}i3cUC?MvYn3s{A{e+4Xab_v|KgXM0I^?OHX(aM=?Sj~561B+uC%>b;PAPcXo* zpj1S!W8wj1?UD@j$<~$s~PL6;`q;7_#e+wKNLb6A`AF)Mxb|Op#no z8_((vw~-+Zhnw2`{`L{>C?goCD84K%w8BZO9bE)9Sb~mZ%)No2-v&`XU=Qz-mYb4-F#F~glhWU>jzlJ4jx!2#oQcCb)of1Fl ze+pBr!tx(cSHr+ik|J0<11u~tUBVoz@FYm#sMg9;&kVK^H4Fh|Z>JXJ-vGyMksAqO z#G$om>uuTw9WK^35@KD!Bn=LKuJxKRrSUtRvnc{pM^x9uMxACyBabk$jBr+EFu1lY zVIJ1QK;BqA%r0hv`yA#JGd+HP4WU{}D4puiex{vf(@xj5P1+gcBoFCbsj@Lz*3%S4 zD77W_?S=n6dPn}zShDxAy^#b#uLuOZA(F#zn0E}5)JC;UI|t#|BSO=ErkzJ=PVZe2 zl8w$V7E(cgkke!t*ulwv>;izw0WeTKOQ2LeXF<7o_Jw|cawgS<1a<1yH;E@$_p1P>AP~nZL#+VCLW1J+_=Mww| zCU?_2kE~!oI-z14X!lWdhU|o@y|4(OGJh{DN7xFvR3Y>DD4bk>#T^Pdp}xBCDdFo{TBLW`FwklnQO36Lh|%Sx&hfJ;^p z=^4-p0}WYC^**4b9)LbJSdsrd^OY|fnXrFK!z4XVVi5KMnW%V(v!f8V8W8V=kb<8o zZ3vyPvE$&I{J}tf{j?4kSV`5iPB@!bbpAg0IZ&rd?^9Hq4Ha-EXbQ2BBv$~_=sBIx zDkFKCLEp=%$4r<*x};7sD9Tq>6tSSSAoNkkM#>Yx7@Y}pEW{-dyer6j2X2ITLAtM@9%^?CCRR2=l)K3Xe*@Vxsp2JG3_pD z`AtmA)2ih;YS{*TsP+PX(=Kyq4{wDbE=`VezSIe?Y=eHP{yNpSLcS}>`Nm#&OF-Q6 zZ(GZ&ng2+CGt(Fb!{)L%-&2d)ogZ{SlFL?NPfL3UPOg>Ck7{Z)=bvk8lbri&Y6-TQ zTATBenp)~uQ=69T{0o&f=Vw&fou5;g;`}?64(C6pOm%)mrSANi%0A9-sO;IjZXqgY8bjRy$r)%XNg>$(pgD`g+{1Lyz^OB^o8Q&2#FIAQ@AVgX{~ z&S_ns*h*5xc-@uSi5WXs@$olowRVi&MKX$^d%QOup!aL{hKf_@-Sf!BTPD+U_ZL^Z z$`N5+KY2v;ZhG#Rcq$qW&9tp0mF8-ZFX;e9n&;R~#kB>m4|Z-v6xL zGcc+chu%L>@4vP4`-}8$$juXJYQTfk z@E6svGfj=yK@Fd&hMUsVkZsiPxoWs9ErlB{qK3b#hBML}+^~@vzE%x^v{Y_rriO1+ z!>Tl$8AwsSG%dGp`(`)6WW%Pk#Doqn}OmbAkEuOXe^A+(JLUrXS}2JIXo&d~N)& z$w3!Ox7LZS4tR_84u^HX%Z9Rdz*B~fcfdpHle+^RFuRZHfIC!Mb_d+3D!CnSwW<_! zz@=niMsr7vbYc${KZ+-G;)D)ZpQwF*QU{r8W7g6Rs8Lm_Pwjv?sxDQ%l!gw9A4;8O z5akknDDQw21;XJ5=?QkO8Lth_*li&$B8J-0XX-Olz4qo^Kn(#zu#zt6!8^MRClFPY&3|epp z1n^A=;^)vRB&-uj5Z1;(L@R|hZ5FK8s$he*3^r=3p6D*TW`l z7o4Fz3TJ9Bz**W`aJKd(oRj2$bCc5G{3It_qWow5Me~eN9$A=>nLPb^x3<*y26_gKcnfdd9HfwgWJbs%7+Z0Jcz>HbYg1Qh7|u zbX6~=@<+IhO6;fprA7axr}g;(Y8`g^$Hl;bj?~magT8~Lg9N3`2B|iGxY-5|&`2Ae zU{mvRc*MWuZUz9IW*D}p;Nhg3Av5X#alUCD@7s@+wY#ypx-bJ59#>8EVthj5D zq8_RmQ&iBXv9dun(tJ#RM+rEHaF~u|CJx`x0(`r8i3w{7Nq0ABxuLayT}}!CZMYlN z?ELqzJs9f54N)vy+OYh6B$qv)aUG<<^;G{A1&=qtWC|2&V248EcmoCtjU^TuOH4GV z2H#09A&>&|6+)UqXcCBn6sIOBer+L6p$#iRP(l1UNCUflk;WH)W+uue*-SAHlcAYo zJ9@hIN=E0K&5AaD96ay~F--#C}3HrGyLDaCB8g}BPs`>K7&A&|Cd`05sEs2}2 zOx%1`;^wOpH(!%~xOr>h=52|auT9*1UE=2J6F2`Vs)JTaGlPsVz%FuVosa{&DS+NX z#&<8Yz&GJ`cJ(nW@(HQ;r(}6QgQ4&@^5UOUy7+hU<^PT`SydMJRhG%B zGELS{7%OHg@JB!auA`b3YlDYd^sgsMu>Hu+k4o=vxDgxizzXd zDBA)n+mgUY8c!wchRRttr)rCr+ z#%qO2q{b&vv>&-o@^*O182JW|OrepEOb3nZphX=gk5A1^ z%Cw(M5wT+%f9RR^ZD7+VLz3hBa8~O|&P>hJqh9izlD?UJwnC;Wg<$Q(4>Isiyf8=T zeLL~vtuTzApP4$0l@diq;Wy5r z%J*Gl>Y^j9Y8gjEVaz)IE5>O45MwkAZ8)3a2cUq4e_f}JRnQ-+VJI#j_Ai3jxEPk> z5@^7s6ooE>_4GUqSHK0h5-!I&xSpcb4qOFy<4Ld!SHn~2fw!?9-p2;`3L81HN=7K6#O!7=-p!CH12RbZc{f}wTd9^x=KM0bj%qqQ z1!u?*e+3AA$i$o?!!knRdK28KMmQ+!*el!QdJ@FV@T%OPW+8#JjQAa<*xK-BxlvL8 zLD*g|+sO<`!ud*hDvi(x^GoE1QfTtYe~1wW3FOp-Bcr2}z9Tva{*zLsD&In| zuo<1!Ed>23%1M&$0s~jM~&&7B6Fu0thQ~aLF*L9Q0vI4!_xZR2GRDm+cEtJ;{!;Ca} z4z=>u>bdk$@S(CndT|ML>O)YDQoD&F$27X4psFB4jO!G~Z-q3YHlb6LZtGG4e=*&P z2%OJ#iZV*WM7ioUr$j4se%>kOl_bSkNtb5&8c}TyrOaL|;_XRuPxk_@@user%)5o1 zVo8bJW$&6dgPmeIhPYDqid8&Cx?zj=iIaKDt*>c&g@?GI=kFBt z3`fdN;pL~peAoAhH6}zI2irQpe^q57SqSxCUQ@Tk$2>Ne*@|z5*ZNtC)n=g;r~}OGeppknQG2E3-^k3{C69-b29#P%&16olxc>2iO1cf`}aUC z-b;Fa9|UnHwBiGB9`1t+DPOq=_rkAmzY>F~q`B!3F^1P^Koq0&| zXN0c1z>b~Z!rd{(d9VTjREm>}AS`2AAXrZi;?M?k~1a zaHxoZKIum2&;gG9VsY(machZgnq!ydN}|Nj=F$h)2RJA~ysbymd`BGnr@{Bc_UZ7g zdK&DX%_lF|kJz6^>`#Yjm;uvq0L;OGP(v~GYIMO`98BhT2;4yVzF}|sk#pc))6>?#EvBbkOLvC`LMau6YLH3e)oxB*tk ztK~H$RQqAF+)6bEyg^K7->kzAF}^w7^3CZn+Dy5lAvOaKlAk}sHyhirgxvv3H>PV; zYBQ+J&}NfsJ_yGi;sdtHYfa7nf#M1ltPD)cf1((9kGQjhVy3(IhzCkiC7))crQA%} zx+7EP_-mhpb9G9VKT8{;zGWr)=5nN^?i7!5g#QFZr0VpSnD*p8@iah9tu}0*c!n!{ zkR+bpCtjkLJ>q3G1C8yoQ~WkVykU$RkTk%yPrRkZWuT1)Wr(+9d-vTb-pLT}YpQpK zfA}!=Tfd#+PZ{Eq=(oSbejBh;e3l`;h<^J=*S8dxW3Tv1#O8E{cX#t2DTWLj2}L-H zjCwYSP!6ntiDM3n7H5MeBf2y&lq!4^|dA}c*QhTIdSou$ZVQ&jf!?6lwjlK*}h%)c$e=J1nEbmjNB#g;WrzFhC9#cSktK19=!ELg`#P>DA zmhoL4#W!}>y3mjasVz70i`&wc_E=mC<8V!h!?hHqcEj}@I0oLO_74cR_sERDPoe3D z7JH}1V7T1EaJkw(Qlew-c5{ufHZZh-*ks~&dlWw$&HuY6`na(-{2b~Ce^2&NGM{c% z*(LJHo$Nyj+f8#`}3Gg_17jGR#_r1mv?|v=sHhDM4Jhs?m@-9P;&hM1DCEC!f z(8p}+l!b-#Ixdbunib!;G>(3_7UkI(a|=P)FL_+g96UabgZ1FGe>iB|^yB^2?ZnMn zh@iJp_;?$vqIx~t0bZJ}nev3w;Z93^(_pc@M{%<`hKHLh9&S$f=0L|hE|hIT2;r; z%4^ZeYthPUZKHYBR;od(m7pD@T)?D|aguEuWGfoQo^r>2)~wH}hcik@qlZCCu*;p+ zZN||G27rD$WvQabR0{pYVAcVQ(Sd9N>we(0`T@C%9ur}ye-@@5ovPfU%6+PQP?Zm> z@=;YjuF5A>`IOd9Gd&}pmCu;97tGqr@>Th|>hp#w-%{ltRQX3$zN^ajRr#SR|D?)~ zRr#qZ|EkKrsqza|{zH{tsq$-c&2MN;e*eyV|G|9!iAxQPR!tbKT9Wy0Gv6KNyKcVs zQ)Rj;GgO(Wf69SoKUWl2ZKzcnZq-JbvmC3+(W=Z4* zs+_FKsj8f=${DJhWsaO}j;v7Sd{tJda)Bxrsj^0uio@D+Ro1GqPL-=vxmuMTRW_*7 ztIB3o`c%1Al`X0as&bunCh+xU0jl$;ncnC`qaGgp@Qr(xy_5GTls)Z8p2h9&Ljt3RXo?@Iopo zB5K9s4^Sw;DLfC;;q<{|M$I_Y&OYe3Hq_0X6DU%zx#T>_mZzXckBTG z=Nn7>Fi>Km)Iu4`1=V})eReoz$9uz@_w02d8A17f%4j^AStTf`tM96S4;3c-76PyY z^>HT?PDc_+M-7s8(n*CQiGie@3Abk>{ac*WKzdW6I~#KZr5QWjFOW7LDoHHS)1!!0 zTbPZYz!$UA8G;fp*gh^qXC@Vm_qO^lM^F(X$)qCb!#sZGI4C*`EQD~9z>Fl~8Rr1y zu5X`zUbwaTwblh~_2XomVq%en#aJR(s=#x+!gew`uFRHHVqdh|Nl}4bhfEgNxooBZ zZ&jk5W??C6J^A&t;FO|RWpMw^@u5yIKjkD7sZ4W}(x&#=vF5ZxCA-spG@#K$lZ9rS zAvk@8>e8bWy)Di`UENeuPn~J2Z}a0!EH`m~mW8u%j-YZJW|N)iqcH~T1A?-;Ho}ex z-yP)RB^J&@3jtEuxL|Kx5uZ#7nARD)uRc%YZP|F--a~9N2Lrdl+2^DNI|t(t zL8xxy#zOE)#|B(rVKvs!LJ>P2abgVGy0-c*AJ&mbn)tg17@$ly(*MUIyLKT?h(u*yY?v%4F9c6 z3p<&N4en0baVNIBHdPibouf6cZdbmn-h68SS75h)i7PGG*dv&wA+nihjG>Yg7zwKRihMELI@9{U zoAIO^lcCi_x8Nj(Qn)vf=#4qy-gF`!-pGfG;73H2#9jtVA)wW%ls)K24@v^qi+&R^ z3j?4*78XfqK^6UQq4LfqJ8o;YpHZ-Os0fRNwI9Rz@HA`^SD8pzNQ1fCFmqIYmw9`_ z?smHMJjIz^mv5F%Cqw%Cu;0W13xl|Ng5x?>rSnBuee6skr97XH+vd%az%_V< z7O!GlP6c78_u1(VU4*!s>{1xlS-2jrVW!3P^qji-sU9@(+Ja?yio!m;UQx0KoB$5u z4Hn*r8_AT~cW`bpbuZkNzu#wqfGsu!Uqjah7Qq10J;cbdpC7G>@ zrd4@RJbicv1?YAse0V2o%YG-?+m}%|hb+7cx3RVmXQ!T31uEaTo0FMnB5uc62`J}# z@Lm(QTX-Mt5X>zIMf!=<`dHd=Yge06?oOrL2cB1*j_kl5##w!r3hoboSvZUj(@ivp zU`btjp;TBOa|Rqrrb^^T)V|_Xp9nst&_6CXs}RfLqyo5G#o;|;+l=0a)E1ptG{sXty7KNAOt-52`rwWfHEv0{A?>VB(7w9#ZqCJ}*Hx-M6Ho19ocA zON)moX40Keb>u4+zKXAr$yiYmoL9h8GpbLmO2BVe_$I!^szHKl?MR=~z0S_q%KwFE zL^MPuS`5?;VJxDP&<|B zRatg0;jQZFCOb>`tau{s^*~uWrZ)FNs!qk!4*RWz-{JSH(z+Myb5bntxy@l!-OLE( zSE{v%Sd6ur^Q8ZOw94Q=vfdV2SLNkTsy_UgMXXTN@s#mb{LRGQE&K!jWRpErm^h;_ z+`-58!6Zwaf6Mx9o7y@%+ctMFLkb8#0bU#6SquNhaW@6jLRzQ5^I=Y-v5Dsj#)?MH z5HTHAZ9p`gCbS|JzzJceC~Bir5_G8!&?hAV_@s=dq-F(wVOru-QKK66i=DwBMEsTn zge~U$0lVL!_ak;@XEf8tQ95JCGt}ZGGuyNZ9I}MxvOqE?XWA##s%(t)u{{#Bq(*oW zD@{kQcJvnB=9hUg-;@QGgoMYh*@;wdcu%&!Kbi@z(J!{jDl1~y-e^3Rqe~g8d5}rH z8lC{u8jzEJ)W%44yg6ROg?cGU7 zQc;})^m-9h@&GqP)uBY{q~4SUOB$71PoE*4+r)R#2|9Zkq}h@)B+R&Q4jF0$y8YO zPcEWyY^F?aTruBh$;Hy{MvBW^pBU6uG*jLhf&HFOIxN{NTX?kAhdjZe!i|`VP~TCFNu8bp ziG8$MiKHw^i)zm8i1Nq_y$YE`ee!oc`<`rE?@3{`C1eNO&Q|tWvR@9+MvUZW&!F2T z?wr!D7NwKVg|#4(O{JKA`Z6ZQ7HCh**^td-Qx5a?>ai%)z&bjWpne%diSq6%EP17W z%IiRMJVAmel~*Y?#Q^!?+;8TRYbQ>$Hn`SwTx^ z3Y{w2yA>LFzFXd7%6padZ!g&LPPLt&qiBa#h^!pL|NS7)P1L zQ!|mf%)MT>TUbDSZJDx_fpVAsYjyZmwbQEZJ;QdzOkMUTB6h6HPDRzXH(8qLi_+?i zFTe-_BTBmly2r0xSnb@d%Ka#Rcj7MfreemObhX^2PH~L)1Zm4g_a?`sGOIIVNBS9+ zp3G)9YhW}sbPmrYKp0#GXSgl00Gu{QUVq8+okyVO0X`l9x=g<0eWnG&U>-x*ZDjmwh3_W!c$ znTADm%pTa&ZP$9;Rqfe-<863eOH=$(knOZbMK{8=X9+e;r7`*Y`2$o@F6(vUvdZJ& z)`?mkc}{AduCljI0Y8)FrZbUEMI86YHfK_WIztIZW2{LeGPH!9WN7y#y3^GtuPT#& zs+#*ROP-Mjee!P>GZCMhP!fz$S>m4cmh`a5INdEvcs3uMrfcbcswy*t1P#NIKgiS7 zMyYPsXJU=2a-&>fnwI=deqW6blayag1T6Wp9Od#XzfoyanMSo`%r=4y_;LQx_`XEH z6K>abQJLFgM>2^N<0~+b|Mj(ep*&sBAtr5HX37oTe_YJxsPcnaD4(<-^02n*m-)k1+LgBKPju33PXGHpL@v1( z&Zhg&!*^c?r95>6QNaij>X8++l!i)2@N&T+T&6}m7=IQATgp7tmhw<}@O7cG5!@j3 zPaGFNlT$NPF@i6XfIsAa2rXQHd&uv9$v?{*9fn_xJnekc!aqq1MgQx{THhv< zKqxSR@4E)@1%K>)DO8RgADjQV_r>`!ovn~Lj3-9$8-tYoKuWVhvpl;mQX|jql_4us z>522q;nBxQ?5>cNXS-P?=T?TyPv9>j_}3Vzs!)|jYONY+QYTuf=T^R^QmX6jpy|+m z5SkUL9>J&rs;Cj;Fv@GBlv7eJI3^Wv`Hh5pHA2bAFjN}yDepaZc~gy4g-SWqz^BYU ziVNLnAC|enxg)ZWV#ui!SWa&mk|yQV8aZ=F&N_sXz2Uh-a^4|SdS9z(^Stn?ku@4P z*x-!`y^d9NVoIH)7BAF*RSn*_ZAdnM9mf1k3UWv~$$jTB*+o_4@*^t0j3sT$al>{~G0|hS2hmGo=r`EUWwS=l2>1ZC3h^qg=``x4)mL6trX^re1mVTxtM+r4D z$18Ffb1!5bldHg_&z4~4;nBO)?&{{)B1RjKWCL^+$0@cNX=YUhm*HycCVLgb7ZGL= zR>>2Lj8Z~`@c^T$jD5l~9A-R}6JsGy^gkus0<4o?%CGdEePynHCAdMz>t4Hw|&kA`R}*Yesv^&@|v3qD5jzClq~B;@6%|Hj(1~jBba48>|>?f&kB10ug0L(X%(eignIdZBQ05mKFsq*)F2lL*Xf86 zJ*OhZ&?8m#O0<3FqU^XzlWSHzt75^_v2X^>@=UI!{WM*th^`rng<*NW-c>uAJ*5^m zdpdonrL>6(%7>LMuhqId-q^qx`Iw54yT{WYsGH<^y0-!6vZq^(CaqEf=a;cae@FhL zA=cy+S>ttoxjwY$qL?2rExIVW0aFR%gsL!iyETZK@WAY8(&B|nxeAw0P7ajDyg(_> z@hx{tFwONB4gXj98{g-$WLup3yZl4`Zq$(ev-){l&pt;sCElpa@VSsi1%El$_-hGefKHpis_U_To6VX!jn2!|YXynl;m-3j3b_RC`t0KW@e|y!MZ99#&1XJFU za5S7)#*|Z5zCj}|(`;)~G7*kcwVBbjWGrSx6IJVX#Ef95*)$`o_8M);gc-{v`_Gh7 zq|JZdDW;%#Q9A%o6frxC;H zj?Q4Bs>PXI(HXAm=!`I_un7jUqe&^Q#fa?=w;2#`DfVXFe_G=X&j`kMGzL3;bOPlF zU9)v6qjIL)wqV;1gQ+kL7xq=l#tNM(X^t)QGmU*M2DEE+X2f?un3-s?x!Ox7F_COPS*L|`3X?Y$jP7bO6QI%G&};WCi5LJ_fEVes zn5y9dZILk0e~a8QBpr?>jP_VC5jLY$&B3SEf#ig`NqtkR+PAixuKo?c*rr91b zs@mh&TaS;Fd*!qX^YhC3Q&8TJ?cAz}(P87Lp<0br>9m?oXPP+3UM4*p&%jw$zFE%K z=~Pc=LRO$*MaGUPW!bUY>lX4xotgx5p$)q^7VZcqf5N*BS!>p5Ev;k9w*cGxFpWuR zIGsz`zFw!ZXam&X(&0j5nmzz2?iPU9M6DWa*6Bk+(BwgPL+{~uQ?g^55fjws=(L5l z!V)di4W`fu@zl#&P^WFwmg1t;Om2%9)@rSnFvvSEX~Yw89}J~kryUf=nz?N!Q$yKc zvKy+Rf1T|b?SeWo%r4j&u39U&n`34>3_k9q4)|NbOavoBjj2;7od;^7$qvi3hDS}k zm*NnJ1dxc4FhX8RA||&*f{0@wAMK<)67%*#0El^rqF(wi#>U>TC5)wXfle3FexTnG z1e~$iEZefakLYx<=u5Z6yAp}R2u8hhDS)*{f6Q&ch?g$MIF4Xs#JqGRSZq&*BcSJM z#BXt4jjlzG=qH<0%xjJW_sPlYu|1g#heS3v>U0y`4C3O67}LsOYbuNV-a@x(bem4M z(;Z9&y;0|^v+8OQl(xsrj+6(KW_9z-BZyQ7KyWnJVR-3dU^-&#HXw^nAa?KV^wKAh zf2EyxxYb(PDzl$vIxcGLO`rMXBqDfPL9kRkKYz#K^ z2ptv^>$Y5}9~C2;u?+Pn3>k(MjJ5&ee`g`GPMAmncD*%jw84!&k052++txlS&w}k; z;Yb9-FM`sL5lrmB_)7q|J%*qk!j~5yaOAu&G$3IYGhe}}P%s?XC$j&VP6oZi)I4mX z8{GS0Fkc5rdkkY&1Ty|6#yib$R2=u);xjQb32XQ+raO@!!+U*n4Siqa`va!Gf0XsL zzATcfudlDoGM%b-GUPs}0aVx4*CS-q9gCr3u8+t9q+~1;t3(t`^i9HUl6iw^ z=753J(qU<%VJFSk=}obozcZaO1nbGBhkW`c{Y#^N>+~P`Z$``%Wgs>LL!myI!y&YM zvqt}yEgxo(>ZNy(wL?a{Ef(&S>dZ^;!Tgb5LPiY8nRQm2BN4~!+_z>se|m^?PHN0Z z1jukMBRC6fP~}^k3-&orYW@!@IQ11{U2R{y2mdFsvDj#+PM-D$|JZup?EkxyT?p zn85MrC(%cmdu%Q@`$=&?=L)VwY-qz~rt`{%uA6=~TRsxKA}{iZI#=;rWU63%Ulir` zmSIrQ&q9uEe)&9~Ptte+nlnBbB|t`ZD!qS}sFNjPGCIh)!=V$+FjTsT7i(Ot^AcW~ z5xdtoDuWn|j!yC2f6VZoTCeu;LOxCB<-9@?x>eyaolusgNXRUMs|9zB&b7P>MVOfu z^RjT-7v<@EhQ@U|*GsFEc1P5*FP<>aV~lphEms|lsFK2QA+tf}MmgZM?RiDQxe!Xu z&F8g90K6_k{OHof9Myn=1w8%$@vKSS@G z*q)49iK1#{xWDU_gzz5V?Ny+37HO41P$>*Wq1 z5lQUs_eEkqe-GT$X`QLgQYep*)KT0iq@uaM?1PnN-3STtaX`{=Lgys!M)@K6Oj22s zxd-vBtY7`)<-I8Q?6_6sbWY;+AJ+MNz5vCNVekq7W@HKRVhl&_Jb ze=XCgLnyB|XR8amd_6*s6oD4oTjlEG7QRvEoA_qACJjbIW`}(}k*Om4{()~n^ffJp z!n;A?Z4x;<%w#m-<2(3HjSuL27k><4KdsymDNCr6S(^(9C3h*4Ov98_bxb%tj+@ten_+GFUjskr+6qI0F+~0@*=zip*U}(2o z0U#Yezz=Htkg)S`20PAelrXV5oe5_iGI|u5+S2hTew?4s_(`3g;-^t>4HN|+#Fka- zHmpL@n=o*w@bMw;()h5D)SYULEY$YhWN}%2e>y!f31W^~Ngx=P3iGp2o+GKfof>~0 z{bm+y>x_6ZlJN4g;*Bw5ci2qE?Yko%e~!N>7Wh2Vw6Z~hnsxWbUk2*tNU}W~6)${I z=dbWrp$N1gEA5Mxq1kb^XkP2%m-uCkzb?Y~Mn>V0x{MuLR+}ykd)kTz-_rTp{2fq& ze=@?QWabbkDX;bN_rR2W5#Z$?2n^{e5lOb2Vr@Uv=}P_))3QM?at5i$tR~ee$bO>p zPo*I9ncJI^c2M>5&k;u*4W>~3QYyYznNAxiO1B7H$iEil{>Boe ze?V~VOeT8OnO-*zsbr(Z_;0COoy;aj zrH-3=l|ok;c#&yQ5!@R7L_-P zo27+(mub>~;ssxtB6Og=a95y7t%;a%BWM}6w`Ki`hE-b|TbQ^WWwKO=iC{Fbe?g)v z5VnNdqd}8%%Vc8-lU0e0L^2i5=lMQ^hc990&@P!=%Cd0~Wl?Kx@UoFnQ3oYmhiP_qJV- zr$v#b#$+}=&V_!C)i>vXtgsmZe_#}5yc7S>6BYF>eUMBtS>4x}Vs}xWy-SV}JDj^X zO9@4NHN!M7dlxbAa6 z8_5-e4RWX*T8e=9h+EfmmrM2HgBXSrvX@y{<&CcG%&8W7)}VB_y*_NWe+{8|_%WkT zb2}i0p>QCzxnb-Q>oQ~3`*Cl(WBE)=j`0rd7{-?$4y_NiuZcAUJ7Bem_W^8}`mvL~ zt)2q+Zo^?J_P?@{bSj$OFVggyO8{;iVWBs4?fLAw$W|WLW^=C(6y*Sn=Ayja)Xdc0 zAN%kw0fx-@W&ayYSu7{je=Aswk4qI@)(g2csUD1}aVRemhrut`F>U)GVWw*`)x`-% z(jVAuIqD7b198J+WN;gs;2ipLEc3&oiqYcAt$$3h->Q1jDp);VVdf&k=;y67Y(f$tjpGNe}lViW=N_xWU~SX zkL8D@r!sARzy2|>-GkR&Y)&g#dP=r@H=NLu9#VQS^fs%d3wwVq(og%DhsXCGPD?7K zCo_%iU9smD^ttxW5+2e@f#aOxl8j4F>P@|UInF$n{e2T!0PcjHXU8-)>mf7;eOW7= zFRjV3N{BQAk#|a^fAjj}rVsx8q_8JHv~$o#jXkAv`^dWY4Pz#==t-+x!DQ~t{AHrM z*VkeuV{OLju=EaN`Ve)Z96+7Z1kbbE1O&Dscu^vrhy^Q!6faVt!aUXCqbVw? zt2PxXqC8BTClfJUovVUH^w+{%m6VCyy4s><7xB^})%T_Hf583!2An=$PG6v_R<*fE z?Psbi8-~H){72;c#k$(4HsRZ)^6fHRZBVoE?F#vJrLNYivx?N!eSg^CI0yD!EBmg~ z)jHLJZ#T%d8+FyJ))uLo(FSIFbLP4}4%{LKZq-$TYAjN>%Z*dE;Dn8L%Ekk_TB&M^ zcsFGEai98xf4W;!pVZZ-)Ta^4dyj>qyUkrjRfAQD2*dLCmf>5*l|MZVVZe}W(B$_aEML} z_~##@e+7YxLv-qs7N$}1OMKGsIhi~(hGtO-=$JsK(nMN@l^U9=d{`Z4<5l@ex8By) zTKqul@)K8wy7T3K4G7JF7fIuT7ie^47p*)%K0MAiNFIIyyK*etJ_~Ls z70^rz&omo?I!+Z>cxI?V$N`a?#;77S8WhXVfAdF^$6KpW&D-S1=W>mf0? zunAd{jgWF63cL?OR51W`(s>RcmpX)0_9JAXO~@ELTDoYXP14yQY1E^ANIDTHtI|lS zbV;goNt%*IQlU%IBq7NIF!$5Jkr8w*2r78I4?!mZ;ewG9lmjpZz2p$I6TTuq$^!mS ze>ZKfz`w>G+Syv^k91S?s!D&%ek9~^n07zmTCGjYsqlzJX~dMNu?{h1YMez(K228R zF{j8Yx8*ioegig_i)UWIzpsnVKR~0c!ICbz=q}3TCj$OUKuSZvf0tj}j`A#?@&M*uV6@4@$g|8v&o7i2Bq zf6jh9Z$G|lKVIynuLk@tTc6(mq!N$+TlV8ScpRqh^}4})68~OCGU@`gL7%QCH)1qG}ZBz^rz4t=u(UjY3WtE+>(AJ{g|%A zC3^xfOAGKb_I7c8MX!?6+akh zvWn;8oIw97XqHqsr-usxe|>ag;2D}+S#gv>*byG55YUR7DtmZJYcWqd%rm-p=Elk= z0K`kj(?p)+2y6nP3(+~4-HLewIjxN*QHUyNyDOwgR0z>bu?U^x=C?Uqp{82!W~ymw zI>?&nk}hjA+_e*&Fo(7GfFMVsDH^|~aUrqBlM0=TUP>%KU~<@7f9?oR4+OwdIZ+qS z=@nWYU=u~sgr&Q5>6X}1b;3-HW~tc@)Pxhy9IR5zb`P$D`n z>*kexlU@R9N~XooX)d;DHa4vLRhcSxIMqqLO~-4zaJ9yZ97+P9q{5}-G00UQHTR+9 z1@d?EYLIcJF@~FNf4$ox^+arFR`9K2BqMGSiEI>!d}!k!l>067xDb@>M~1uz5$obK z4w_vKnq3Z7rf~q5c$=n!gGr+@d5|9rI0jxT)tsdxn}@A%MBf0Ch)JsCN$m>Pdk56}7;Dx(%hQxO@?yR+g9K9_4UW$oKRn zEh=&$7dbMOxsxFe!CS<)sfCt4iufqzavi)Pty9&aY zi-EUVEphN}e*h$zFA%(wkFuE+>(g$q!%i1#fs3_(Z*jOS0M?~&F&o!1;5uypjm5HJ z`oa)2wj84svfqNbNNgC-e2MT!7!6fFFMA2kmo31$z10R>q@=61iQ{pI#?r;?b5ge? zj8ltw4|3?fF22zJJo30?>|ZYSlz5z6ex{7nCli1k4U7Luj}R;?DQ?)K3eGvZk^oCw|DW!ZE=3G!~5|-HzR#+j#yUfh&jHdr=o)gm`CxCfwng#`a2`V9??SJ67_C+}jP3|ZlcTOk zK#|M-7S}wtxaPUVHc#x_q?)Y4<`kN()?k!te+l3?Qt!xr`jmQbIcG56tkycFatWj+ zLfCIHpC<}UV;nVtqc=VKZ-#cb8-hxxf);mok8IS8g6fN&m!pFV`<|6!EfkDzLL ze-!1*W0ruOXOvEPjv`|YR!sCz9@L1d@~MO2z4std>?Rdk+|E2xLzOe}GCcf>M!ZMWrKRj|6g50>%6#N8T@V z^VfRi*FU0{x-u$tWmM`$^insXmpYMEf8?=O(Th@#JGZtrSeS*%xQIkH&kMB4&O7XJBN{7r}Jfih&j$esatgIk>K0%YNS zrc=2$Ru=PjD{S+(4f*@<{tLVLm2UoV-%|b#80(!>Ob61ct(h+3nQq8(dhQGoe>SR3 z4hiiJ3DaE?{8v;A1^tZxeG?VZ&1vYTyXdFeRSfo&WI^AmHaj_IJ>=yfyR_?Hec6(| zC@p99FY9FPL+WfN&lRb2CilZLwa7g7Y5SOs7YFKk8TSbdm( zb%cMLLmMj{hI7&w?m(p*Mel5;e=$7IC1;*Pj-RH2oUKk8@DjgGl^R`>#=cK3?;ZBf zbNYmF0r37uMjrpmM%37ifl6tkp0sl;N`W^rC^;8%K^jZjTuKTlPi+J1B7|{Pmn472 z*`1R`iAJhP<2UkvzDJoQ!xw^_3UE_|fS2sDMkE+|8(H-h8b&q8f6&<}ZypB#0H+cF015yA0Ap`%W@%?G zd2MX9SYL13HV}UgkneCv*ChdLCtHy{xM+bjf4TwfYAk8HJr%S>$($)tBPlz6+lSaE z+$Y)ZNJ??qWY|Mp6qZCC?~ZrB``sx|=*3t4x4-vb5aCJqJQ6+62FJ(JpK^+C@dtcAtV2%+`U zlashMk+-60wVV{TIoRKeJ0Nqwn|2YPe=8iQbCPasU2Exi=@Mtzkl*+qn{;+ko~lmk z4NkyfVh#uCciT~E$=Et*J_Sqe5#)CY>=(|NQkgUKw7t20GUZ5vUPM z=^)C2i0l?<$PpO6t**A zyjG>`t-oQT^xKS%zMGuV3u3sI!jHNMghXX8i~TxwpcPY`FN3F{DXGtPU*6@@H#%AbY0kRrgIIj z>XRr)sLBS959wkBS2wTde9@VuA4jB8!4IUdWn5^X8rI6{5QhL>;xLF>e=rS^#i<-6 ze^}eSP1{C()LfFupGG!wFb~&WDH>5rl88#ybu?SesB%i05*etJlQE0^%9vNCR0-{4 zD7-C{&^Z)WQOGmkLI*sTkOhx6$epP96mzG_8hGc^WH#g~1qH5DkdFoEdI8tDSo)(< z5Gf+DyeO!kSqB0KQUG8Ze-T2)2rrFWCo6R;{aps(5dZ+s`z&4#3q!i-=)|{TZANsm zuw11lUWR;EN}ccKz#}f973sGqWEI`FAV3M{+{@h_e#^VPG+T93Kr4(DGvz zoRGO_i%~tqYGnM?_S1%~qA`N&FFIvnc`q-b3D6Z(KsPqq0l~ii1FlW(z1tGPnH%nz z3s^;U8>4@Pj1qNq(r-e=1Qkl1%Yn2nM_M-J**+28JVFKUN_&&F1&WD>q45!r5qAk| z^UjvigAgmyyhD;yV~$QF0@K|VOC=O(eS0!XE*a7D$$SX?h0C&Ij|Nlt=&t*868u%& z;iH8zz?$0Eqnh;VEVdXf82?CMvu$6AkKHTqx1y+plgkweW@cX%iHMz=Lck*w~mldy(u2w9_AjPlWcS#h9B1Qc3Lk zc&Lm`SW%FY)f3r-LOQ0JgixDci>=$Ekg|#!CJ=#}q0=&m&km+195#{G&!5b&1P`|k z+HbF7-iVUERzh{nr`hvMxxwCf-4tbi0YwBy4v#%xm?9LXh%9tQ{*l4p;+lAw z^CDa61+V>lG;Z{y^QoTmp9j-lDTN*dz?1L3NB)$fqEVmovHA*Oo2|Vs%00{t8JAvl zeF9}yB;UAy?l;D|(Z_;;xT+?o3=ev(@SW#xPY}etmNM^g)##{1AQ3Ybi~aka47pTF z!SoHv7iJYUDyL3{48jS*(-`a0amFi(S5;E5ySJ~L*#UBojam%RDWeo8)av~c5b+w4 zMSY!-DX}ZbrHA3K7Vzv;#!FfOBS$d_txHANTm5G2laT<4XEbC!+kje}Twk7-Z7884 z(PlWa0X>zV6?-Qt^?~`{Acl^4!YA85CZmD*KRwmK#?{=??*I4HYaRKxe?7JPOkp-D_9m|U5h+Wza7S5!qGgIr~^$s&N?lwPn08^L^R z^pEO7pl+gKD`Bg9@6L9uf*cLQg!RM0^GWISC1z!y)2kXww{k=o*0=I}0Ie@l#)e>JmlqPBhnH+UOxQgr_RPB;G zsYldRa}q7`qLX98#tCrwfa~v*){n4jnTT!$Y%Y*lzaJ&aQToIX?L=Dz)RYK5(+9H; zKY4*go>glnTCz=t_WHkkj%jM5zJynQ{PMt9Zn)|1|I1Zv5n2uznHX^QJ;Jf?YP44rHX8N3H}gl zvz_KtGd7-lTAY2*JRgQnWmZ$vnpB`Bw7P{o6@^(jZPYqQHRj=Eq0te$DQ7qaMgVY3 zzio`n<=8Nut7{8&ab;5SDb*#^6TOY6o>@DZh2{m#08QQrZ6pdbmok$0i!M(PXGsA8 z_f07IRXwKO9;P*?+OM-&(qv`?V!ES0o4QCmW%fRM|MF;fzI|15d=+yzXKSgO(d3d2 zVyfPG{_sacLCD#TTwWfkPTcf~Sb#$WZqrS)Y*>(>MtL;z1;PdFuu^N4z8Z^`^~e!2 zl02VHoT()w&q}x}3*^Qw46c)(DZ|kr_9VXXx*B@+t6kGuq4q5)x|ORftt8Ljm>7T! z3e;a3iWs4@SSGA}6`6XzI3An_pP5jzko*DdmLc%)pt8eV5c4tHB|O6rFTk)W0vjl^ z5+xU!C?#1^U1XhI0IKzA!Ak4aL35F%+y?H}lSW)f?oxu5z?%1-5BBX#-6@q71tx2$ zfRB+`0yz=&rbs82W0_%T6qAXAG3@OfvsiTzq=r9snTEy{k&Si2Q4*#?t8~iZ%%~kT zB7|AWd><(uGuP})V4OUHFd&>xhOs$G&nVB&*74P- zB?2`$IPgr(GR|^~vOH9m^9Zy3Fd3nDr5<4W#cv{#C> z4P7@b6Etep>uidOBmi*QmU+=0CtGnP|6)eJ5kAmiRD%oHbmm2&?qI7zRJxpnv+^XS z*$}Mz1znnWAZtaZ=<|0T(!NFB(NVsyLf$@BB{1WXd^o6MwMQUc^?&q)wfXR42PyB6 zjUj<265p=q6b8niO^VatWcOWsOTuoRl#F@{s#?j>L`mO9F#x3ho)MF5W#n&(*<+OE z6v|50ysH0_5QZmDs@u%`>r!J@fJsYLl*+~fh6_tJ;tc{)`h?vLcJ>zA_7ZS?Ff&mf z-8l)inde}UEcP}!HIgE%_CRN0vEoH@H?(YYS;@pynD}xCs8Ftkh~w^ick`|RrO5BK z-!|ZkW93D01OQh3tzRaL;>;PD=VWsU#}12X15eoPLwfIiv#4!0gYd9c=&_69thw>9 zxqJ1Nk!636fm?px?QkPIlWE&{Cib5y*#%KT>1eF7K7dmi00+~EY@5_Mw4}2NK%JmoU0daP`KspPg0}k}=h>)^$O8~NLu8}h*dJ1IHW_A_{QULN z`1EqxUJShLd;FgruNzOgN9MBdzaPw5)CpQB|j}wsR;FP8B&3k00HsRz|5AR}W951%UptZkQBz3eEatX2$S|)RevZ%x!jwt{OIA)j~g#pA$O^d8XU8DY0gH${O zxPlkOoHMnonB|=q2m4ywelT3qraE zEfQc3Tmt`3&!y89ptZSj&8JRvsypcFSKL5EqFKl6?Qd;2!x&(x6H25j$?`kvF+Ti} z>S7Lubq4|2^Sl-ZwMG-S=DR4w}Qq_kSse#8S*ez!gzYbAeoRy2vr z>FK$It|t5yl;(3a)PKWo6ij{ixO%Ie*H4|t1H%d9umx%5um#1~hqtucHDLZndp>ai z0nw!PGlAiyx+;JX03ZhHER+nz;^~CM9A$-|p{1Ch$Lh!nJkr~xYtpA;8i5=|tD9Fk zR$BsfuPBz^D_SVPEUbSORWGcx%-*;y-+WXw_58fuWRJ_SU@!En?q1e<^?KjD^)%Y= z4l#Z|&QV5KwEv|Lv{WlntrsR&n^1FXZeOg5%Ba8TaOJJ}r@)pjSuk~*QqUVxQ&aEO zq9j~AXQtIkh2s56p}W#txQNY^-i%mg4^8~5z{|ZynH@=?jE&8cjLVi@!ZeNR{%dk9 zW8B=!oGE+WOGE@7IGlMo4@llP1Fg}`_9{ZhEckq?;zEO5mc~-@azKGDD{{12vtdSD z6P`xFC$C?T6<|@KK#A?bqRuis(`mYFN1FxW6?je-54B!@Q+Aw__&v0w&5rDK$doKV zVKVH#VOS}w<{l|rx?m~yJtRceH5~Dxo#%8i^!Ji2%u*QGZ-rNZt-CyU>t{Mg%zTb7 z`vlj8g5H3K{>QI#b^|QTU_rDpW2KohEbSzkv?C104Nx&^T*^$q38^8G?ZDXNr8Oa= z^&-oC!L_re4ZZ;!Hs`-!+1-z}LPLv0>PDU_|q{}G%l2a-jVW-UU1vgKi% z&(^XNtu&l;&y z3!rf$Gdy4=n}{kawCWgQkJA)109CKYyOoJMkPc6KJ%y2NaG+&aT3fT}82^3IfhwdK z1!#92P()rm^nzpv7@bQ?SJcs0PIq7=f44wogXuI7v(qg}v|dn~^mLAhaK< zKw}&+n+hDhLvb(cV(#=TMp7m*3P&V<01BzW7NBe>W8J$gJ3SI}-4^{7R&iYLb>q}n zooX;4p>ng$*i5T0jWV>BwYGU63WYU$l{I$<)zs70pD_0SnmH_(-|XxVvP1S|=k5r# zUOY+cAGP2qs6`}!Fri}CyTbjR@6|o)eG8tjQhdH1JIZLqa-rNV=9`h$EX&+F2NZgz zU9jG#)^!>~)#hbGl^Agi#b)pptQ_olLH(%<9Dfy>yW$9lKNFsbL7@A)=1I5iygA86Pg+Bb0`4DZ6`81z-?hGG-U&JtSjPf)}28!+9pE zq9T(prVbszXEqlGeh{VAnMhowLbC(FenR#NEdLEADqtBcE@JeWP%$%e0hn1pm*ppC z6Zq*OhZTfhh*yAw!zl?GLnTdUO&=#{gBMYCtJT%%)^I??eR6yc?z>Lw%gn6I{5ng| zVLnrRvNVY(T8wo{i|+x~0Z|jnWj}L%^0bL4tuuWC1yfB4G;mc>2lS|J^WAj`kCGw29%{74ED*Gk2s9K&`!45Uo>?Q6?@E(N z{k)UBMlrhwR@`<1_ishs!FvOC?}m;Kfy23tR^+uYnrix z@9LH|f8F`f`chmVtz!yFeD59fEd(%uX@k=F*ks9Fc6&X|J!jz?vKAvkAIe91%zHX27Tv`bwI6~_&)K7oM$8@OHni=_1n}@WKg*ada}H?VVO)h z#KEr@!%wZNTGcUlmza{8xG8{S-z4G399>bft|_@}DPmuZ$D@Hrw1gFwY^PdN*D!fh zkdR@?6Ns46N=%MJQ`!syX?dGr@u?y^37Xa^N@!d($c=kY0nU(fOz)_DXsQBYL(i`w zMrJF@P=0NI<;2`2dhH()C7wJ@)H^G0zQUD|zljv>Mfl~+nqPGF?a}Q2B!RTaLCBaJ z4_s>bG>LMo;&dswrL05utCMEMVT$&jJD{4e&W8#eHp1zVZ2u$c6Pr4(>eAW&zGA6r zdH#hgZgqwi0L0B=;=)2=i{&b-xr7YpV4SE~+<7Y+_Uzc({$*}{Q7;w_-!G98G*9o* zzRDE!MOQDjDmGTaQ0n)!D9?Tk}CJayqJ z_n4D61_;+1ksxoGQtWl{X6w;5tc_A34ws`>N=Lr@?#Hs z`dr7crI_NK2G2-4)~n6<%H}}ZsOjI1=gw~Wbit0LLlAs*200P11^JpaO-8&J(zEI# z-QyDqMVtZ$)h0{3I#mb;qGZ%ap|q~z`hf020njC{vo$i;^&QA;T^)@lKL>iqi zDso@$=+lzl;N`Nxaf+@2R?&IQcwKclx{UZYnnQhJT+Do`cj!!ZEbeTbCbbgcWuXJM z?Cvt(5Q?}ms-A9><%CB0w(Bc2M_hm!ul0znqSA$JxQqj576ZeendA|v~3bl>G z0A4tri;S?!87B`x>FEoz^Spc_mbvAf4j^a)KsJercC79|quZ?#vXw$)-h&e#R4Q)J zKHafCiEfi&C|D}#iP^ot9%Bg#+5X1&->TuLKIUh=AZ;l5#q}dTdYh)5OOun%)M$2s z=E5G4!54F|seSqd2@;vVDv7vh$IM#*0Y?15Vwi7#vF-$GAKdd8`7P~@z79(DM=Zq( zw5RY<0x~lJQrPT-*ruOgg5{5@D!&CY5}!wHjI_1c`{${61zi1jd}p)L_YUvXdU5<% zo(;kg&-iTagawion~G~&C;3&*U$&+$L~xJN;&4BqmA}b&?bNd412L0qthr zZ6Zcyc$RH%jdIScmZg)i>B_Ch;d!J&o|y63XUH`IjQ4)QfZj5S zP?JW>=kZzaVfF0SaJlEK0FHA{U^dzQFP1=$qcl2--VERNv|*Ff2sso>sFJz*p@jaY zS|q7|npu1eq?_EK=y844*UJG>0J6v3BXiHtEaL;G>vu~3Zc2wLbB6no0&hJEkc%x2 zV0G{I{skGfOf-Brc!GVsL{&)h2J%xh8(?x5S8OGZ{jA%eLyO&D+Gof4Q3XWzsd^5j zXVzh0^vl$f2l%N;koEQS6E@hpmrO@0@kCz4c`-Bv;?Lh-Y2>e~g*?$)fXnnPH}TV4 znfuS|{$5N=A8ATm{~xMqV6eOU-1)VQFM)5`@Jes{adrUbtGb_j&2E*)P>S1LRmH&I z3D$+cci52tV>jJHEW_a{Jp75!Mz}}pzX9kkJX~{uYN-)_Hh4!$U0Pvnf_|GJxg9d< zB{kMx>m`3jreo-mFiX?*%S zuZCLnoMe0rCk{FH8$5T#>;@A|Dcp&42$IR_Clv%NQ}2WT z86KWgmZ9GB0-m>wpMZ1r+R87&*Yvf+i3(I>jGKDc9dVK2LJ&)l)<5tGB}(?R60D1%;&w4310_1Y2rTT>pFe_Y{1PBFu4^>y!F7-5J0r>$kuM;z~}y= z1+@^L7#UU#SZ*mRBdMgJPx%6>4z{LDaBS?Imif!|M=Zo`JxQe+R6b$F(t`N8MSiD- zsp)yc6n%@kSgcb9sN|n={&< zPMh~wq*Y*S&A0jLhP|m-?PBrWKptYNlsm0Z(k}`RV7U||4LLeuG`85_GRvAMYzH;+ zhf`Udv>)G$Weau7RI+fP9~7?@xcb0K$|XiW${Jc%7DroVGyO3P+D)#;??O8nCm*eG z^2-|Gt(xQGy4|MJBl9qii9{hWuO+(> zdFR&#z~8O9MmJ1aq*LGvRF$J;vfF#Rji)*k^5@a&ZjO$EmD#XJXI&F*5z``M>tVD!nQFDg`oGA) zZk+PTz4?P)#h?2yIlynC$j?pZR?l4tEueq|z@D{V*KMA|NjKc>ze6R=E#GZ>bV}vp zO;P6&_39(G59lrDT}k>hpd{9$!i3(wnEqij0 za0kpCl$$%eiu?5968@Pkmzb}W>vX{rjj~pjUWr)}++q!Jhj8~cTcJyu9d-dHKe$#T z`}~h~f*@Cx=y2UKn&vTD7w5c-RhSA@rfcJNbt2urYdNtHt1NIC;9xzIa}Z5XSV8bm}k@wptnkB zhWx1Ctor+M;V_RjDOE_?{i;|xpfl`zhtk>#lYs|}8+xvIu!+&(#l?$Lj#ms(P)q;n zK&Z}%DnCO&NL(hn5$({`pCOQy^^RbkhEQD{;4Y}b3OS?23du59 z9(I<&h-AC_+93spl>zotXEymx0Cd48}#YR+$s|CoZAO*t`#0q3g}1rvWVAvD1g?{ zgK*hK`kI1I6%W{LyY0mVln4?VV1R$$^z^3q{gir{Kiri%{(+?$2l8R)%LPvA^ryxa z>I0XX4L*}yd@T|e{Z0KKcen^v0G2W0Of;{jou}dRjx@U|+jyr^-G^>PzL(+4rd5

=9KyVbrwxLM5n(7~C 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