Rename: RackedLimbo -> LoginLimbo
Plugin renamed for clarity. Same functionality. Old name was tied to racked.ru brand; new name describes what it does. - Java package ru.racked.limbo -> ru.loginlimbo - Main class RackedLimbo -> LoginLimbo - Jar LoginLimbo-1.0.0.jar - Command /loginlimbo (alias /llimbo) - Permission loginlimbo.admin - Log prefix [LoginLimbo]
This commit is contained in:
parent
8cd92694e7
commit
9e0ed01321
17 changed files with 101 additions and 101 deletions
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Report a problem with RackedLimbo
|
about: Report a problem with LoginLimbo
|
||||||
title: "[bug] "
|
title: "[bug] "
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
@ -14,7 +14,7 @@ A one-paragraph description of what is broken.
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------------------------|----------------------------------------|
|
|-------------------------|----------------------------------------|
|
||||||
| RackedLimbo version | e.g. 1.0.0 |
|
| LoginLimbo version | e.g. 1.0.0 |
|
||||||
| Server software | Paper / Purpur / Folia |
|
| Server software | Paper / Purpur / Folia |
|
||||||
| Server version | e.g. 1.21.11 build #142 |
|
| Server version | e.g. 1.21.11 build #142 |
|
||||||
| Java version | output of `java -version` |
|
| Java version | output of `java -version` |
|
||||||
|
|
@ -39,7 +39,7 @@ What actually happened. Include exact log lines.
|
||||||
## Logs
|
## Logs
|
||||||
|
|
||||||
Paste the relevant section of `logs/latest.log` here. Search for
|
Paste the relevant section of `logs/latest.log` here. Search for
|
||||||
`RackedLimbo` to filter our log lines. If you are running with
|
`LoginLimbo` to filter our log lines. If you are running with
|
||||||
`debug: true` in `config.yml`, please include those lines as well.
|
`debug: true` in `config.yml`, please include those lines as well.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -48,7 +48,7 @@ Paste the relevant section of `logs/latest.log` here. Search for
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
If you have customised `plugins/RackedLimbo/config.yml`, paste the
|
If you have customised `plugins/LoginLimbo/config.yml`, paste the
|
||||||
relevant keys here.
|
relevant keys here.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -41,6 +41,6 @@ jobs:
|
||||||
- name: Upload jar artifact
|
- name: Upload jar artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: RackedLimbo-jar
|
name: LoginLimbo-jar
|
||||||
path: target/RackedLimbo-*.jar
|
path: target/LoginLimbo-*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -39,6 +39,6 @@ jobs:
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: target/RackedLimbo-*.jar
|
files: target/LoginLimbo-*.jar
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to RackedLimbo are documented here.
|
All notable changes to LoginLimbo are documented here.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
@ -22,10 +22,10 @@ Initial public release.
|
||||||
are released 5 seconds after the teleport completes.
|
are released 5 seconds after the teleport completes.
|
||||||
- Optional 5x5 barrier platform at limbo spawn so unauth players can't
|
- Optional 5x5 barrier platform at limbo spawn so unauth players can't
|
||||||
fall into the void.
|
fall into the void.
|
||||||
- `/rackedlimbo reload` and `/rackedlimbo tp <player>` admin commands
|
- `/loginlimbo reload` and `/loginlimbo tp <player>` admin commands
|
||||||
gated on `rackedlimbo.admin`.
|
gated on `loginlimbo.admin`.
|
||||||
- Shaded SQLite JDBC driver (`org.xerial:sqlite-jdbc 3.46.1.3`,
|
- Shaded SQLite JDBC driver (`org.xerial:sqlite-jdbc 3.46.1.3`,
|
||||||
relocated to `ru.racked.limbo.shaded.sqlite`) so the plugin reads
|
relocated to `ru.loginlimbo.shaded.sqlite`) so the plugin reads
|
||||||
AuthMe's database without classpath collisions.
|
AuthMe's database without classpath collisions.
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
|
||||||
20
README.md
20
README.md
|
|
@ -1,8 +1,8 @@
|
||||||
# RackedLimbo
|
# LoginLimbo
|
||||||
|
|
||||||
Auth-limbo + login-restore fix for Paper 1.21+.
|
Auth-limbo + login-restore fix for Paper 1.21+.
|
||||||
|
|
||||||
[](https://github.com/s8n-ru/racked-limbo/actions/workflows/build.yml)
|
[](https://github.com/s8n-ru/login-limbo/actions/workflows/build.yml)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://papermc.io/)
|
[](https://papermc.io/)
|
||||||
[](https://adoptium.net/)
|
[](https://adoptium.net/)
|
||||||
|
|
@ -29,7 +29,7 @@ chunk is loaded, Paper's safety logic then snaps the player back to the
|
||||||
nearest loaded position, and that position is world spawn.
|
nearest loaded position, and that position is world spawn.
|
||||||
|
|
||||||
We tried fixing this in AuthMe config. We tried removing Multiverse.
|
We tried fixing this in AuthMe config. We tried removing Multiverse.
|
||||||
The bug kept reappearing. RackedLimbo is the long-term fix.
|
The bug kept reappearing. LoginLimbo is the long-term fix.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -62,8 +62,8 @@ all of that.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
1. Download `RackedLimbo-1.0.0.jar` from the
|
1. Download `LoginLimbo-1.0.0.jar` from the
|
||||||
[Releases page](https://github.com/s8n-ru/racked-limbo/releases).
|
[Releases page](https://github.com/s8n-ru/login-limbo/releases).
|
||||||
2. Drop it into your server's `plugins/` directory.
|
2. Drop it into your server's `plugins/` directory.
|
||||||
3. Restart the server (do not use `/reload`).
|
3. Restart the server (do not use `/reload`).
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ environment variable form.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`plugins/RackedLimbo/config.yml` is created on first start. Defaults:
|
`plugins/LoginLimbo/config.yml` is created on first start. Defaults:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
limbo:
|
limbo:
|
||||||
|
|
@ -105,8 +105,8 @@ Full reference in [`docs/configuration.md`](docs/configuration.md).
|
||||||
|
|
||||||
| Command | Permission | Effect |
|
| Command | Permission | Effect |
|
||||||
|----------------------------|---------------------|-----------------------------------------------------|
|
|----------------------------|---------------------|-----------------------------------------------------|
|
||||||
| `/rackedlimbo reload` | `rackedlimbo.admin` | Reload `config.yml`. |
|
| `/loginlimbo reload` | `loginlimbo.admin` | Reload `config.yml`. |
|
||||||
| `/rackedlimbo tp <player>` | `rackedlimbo.admin` | Manually teleport a player to their saved location. |
|
| `/loginlimbo tp <player>` | `loginlimbo.admin` | Manually teleport a player to their saved location. |
|
||||||
|
|
||||||
Aliases: `/rlimbo`.
|
Aliases: `/rlimbo`.
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ Aliases: `/rlimbo`.
|
||||||
mvn clean package
|
mvn clean package
|
||||||
```
|
```
|
||||||
|
|
||||||
The shaded jar lands at `target/RackedLimbo-1.0.0.jar`. Requires Java
|
The shaded jar lands at `target/LoginLimbo-1.0.0.jar`. Requires Java
|
||||||
21+ and a Maven 3.9+ install. The `lib/AuthMe-5.6.0-FORK-Universal.jar`
|
21+ and a Maven 3.9+ install. The `lib/AuthMe-5.6.0-FORK-Universal.jar`
|
||||||
in the repo is referenced as a `system`-scope dependency so the build
|
in the repo is referenced as a `system`-scope dependency so the build
|
||||||
does not need any private repository credentials.
|
does not need any private repository credentials.
|
||||||
|
|
@ -144,7 +144,7 @@ Multiverse-Core is a 2 MB plugin that does world creation, world
|
||||||
listing, dimension portals, world-specific permissions, world inventory
|
listing, dimension portals, world-specific permissions, world inventory
|
||||||
separation, and a dozen other things. It also intercepts teleports for
|
separation, and a dozen other things. It also intercepts teleports for
|
||||||
its own portal and respawn logic, which is exactly the contention point
|
its own portal and respawn logic, which is exactly the contention point
|
||||||
we are trying to avoid here. RackedLimbo is ~400 lines of code and only
|
we are trying to avoid here. LoginLimbo is ~400 lines of code and only
|
||||||
manages the one void world AuthMe needs. If you are already running
|
manages the one void world AuthMe needs. If you are already running
|
||||||
Multiverse for other reasons, you can ignore the limbo manager and only
|
Multiverse for other reasons, you can ignore the limbo manager and only
|
||||||
benefit from the LoginEvent fix.
|
benefit from the LoginEvent fix.
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@
|
||||||
|
|
||||||
| Plugin | Status | Notes |
|
| Plugin | Status | Notes |
|
||||||
|--------|--------|-------|
|
|--------|--------|-------|
|
||||||
| Multiverse-Core | Untested | Multiverse intercepts teleports for portals and respawn. The two plugins should coexist because RackedLimbo runs at `MONITOR` priority and overrides whatever Multiverse does to the post-login location. If you only run Multiverse for the limbo world, you do not need it any more — RackedLimbo's `LimboWorldManager` covers that case. |
|
| Multiverse-Core | Untested | Multiverse intercepts teleports for portals and respawn. The two plugins should coexist because LoginLimbo runs at `MONITOR` priority and overrides whatever Multiverse does to the post-login location. If you only run Multiverse for the limbo world, you do not need it any more — LoginLimbo's `LimboWorldManager` covers that case. |
|
||||||
| EssentialsX | Untested | Essentials' spawn-on-join (`spawn-on-join` in `essentials.yml`) is the most common conflicting feature. With Essentials' default ordering, RackedLimbo's `MONITOR` listener still runs last and wins. If you see the spawn-on-join location winning, set `spawn-on-join: false`. |
|
| EssentialsX | Untested | Essentials' spawn-on-join (`spawn-on-join` in `essentials.yml`) is the most common conflicting feature. With Essentials' default ordering, LoginLimbo's `MONITOR` listener still runs last and wins. If you see the spawn-on-join location winning, set `spawn-on-join: false`. |
|
||||||
| WorldGuard | Should not interfere | WorldGuard does not teleport on login. |
|
| WorldGuard | Should not interfere | WorldGuard does not teleport on login. |
|
||||||
| BetterReload / PlugManX | Avoid | Hot-reloading any of (RackedLimbo, AuthMe, Paper itself) will leave dangling listeners. Always restart. |
|
| BetterReload / PlugManX | Avoid | Hot-reloading any of (LoginLimbo, AuthMe, Paper itself) will leave dangling listeners. Always restart. |
|
||||||
|
|
||||||
## Database compatibility
|
## Database compatibility
|
||||||
|
|
||||||
|
|
@ -47,5 +47,5 @@ implemented.
|
||||||
## Reporting compatibility results
|
## Reporting compatibility results
|
||||||
|
|
||||||
If you run this plugin on a configuration not covered above, please
|
If you run this plugin on a configuration not covered above, please
|
||||||
[open an issue](https://github.com/s8n-ru/racked-limbo/issues/new?template=bug_report.md)
|
[open an issue](https://github.com/s8n-ru/login-limbo/issues/new?template=bug_report.md)
|
||||||
or PR an update to this table.
|
or PR an update to this table.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Configuration reference
|
# Configuration reference
|
||||||
|
|
||||||
`plugins/RackedLimbo/config.yml` is created on first start with the
|
`plugins/LoginLimbo/config.yml` is created on first start with the
|
||||||
defaults below. Reload at runtime with `/rackedlimbo reload`.
|
defaults below. Reload at runtime with `/loginlimbo reload`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
limbo:
|
limbo:
|
||||||
|
|
@ -139,4 +139,4 @@ diagnosing a player report; noisy in production.
|
||||||
|
|
||||||
`limbo.world` cannot be changed without a full server restart — the
|
`limbo.world` cannot be changed without a full server restart — the
|
||||||
limbo world is created during `onEnable`. Other keys take effect on
|
limbo world is created during `onEnable`. Other keys take effect on
|
||||||
`/rackedlimbo reload`.
|
`/loginlimbo reload`.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# How it works
|
# How it works
|
||||||
|
|
||||||
A technical walkthrough of the bug RackedLimbo fixes and how the fix is
|
A technical walkthrough of the bug LoginLimbo fixes and how the fix is
|
||||||
implemented.
|
implemented.
|
||||||
|
|
||||||
## The bug
|
## The bug
|
||||||
|
|
@ -34,7 +34,7 @@ upstream as [PaperMC/Paper#4085](https://github.com/PaperMC/Paper/issues/4085).
|
||||||
|
|
||||||
## The fix
|
## The fix
|
||||||
|
|
||||||
RackedLimbo intercepts the post-login flow at two points.
|
LoginLimbo intercepts the post-login flow at two points.
|
||||||
|
|
||||||
### 1. Pre-login: pin the chunk
|
### 1. Pre-login: pin the chunk
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ teleportAsync rejection), the ticket release runs anyway via the
|
||||||
so we cannot deadlock with AuthMe's own connection pool.
|
so we cannot deadlock with AuthMe's own connection pool.
|
||||||
- The shaded SQLite driver
|
- The shaded SQLite driver
|
||||||
(`org.xerial:sqlite-jdbc:3.46.1.3`, relocated to
|
(`org.xerial:sqlite-jdbc:3.46.1.3`, relocated to
|
||||||
`ru.racked.limbo.shaded.sqlite`) means we do not depend on whatever
|
`ru.loginlimbo.shaded.sqlite`) means we do not depend on whatever
|
||||||
driver AuthMe ships with, and we cannot accidentally clobber it.
|
driver AuthMe ships with, and we cannot accidentally clobber it.
|
||||||
|
|
||||||
## Code map
|
## Code map
|
||||||
|
|
@ -109,7 +109,7 @@ All Java sources live under `src/main/java/ru/racked/limbo/`.
|
||||||
|
|
||||||
| File | Responsibility |
|
| File | Responsibility |
|
||||||
|------|----------------|
|
|------|----------------|
|
||||||
| `RackedLimbo.java` | `JavaPlugin` entry point. Loads config, builds the limbo world, registers the listener, exposes `/rackedlimbo` admin commands. |
|
| `LoginLimbo.java` | `JavaPlugin` entry point. Loads config, builds the limbo world, registers the listener, exposes `/loginlimbo` admin commands. |
|
||||||
| `AuthMeDatabase.java` | Read-only JDBC wrapper around AuthMe's SQLite DB. One method: `getQuitLocation(playerName)`. Returns a `Location` or null. |
|
| `AuthMeDatabase.java` | Read-only JDBC wrapper around AuthMe's SQLite DB. One method: `getQuitLocation(playerName)`. Returns a `Location` or null. |
|
||||||
| `LimboWorldManager.java` | Creates the `auth_limbo` world with `VoidGenerator`, sets game rules, builds the optional barrier platform. |
|
| `LimboWorldManager.java` | Creates the `auth_limbo` world with `VoidGenerator`, sets game rules, builds the optional barrier platform. |
|
||||||
| `VoidGenerator.java` | Modern (1.17+) `ChunkGenerator` that produces empty chunks. Disables noise, surface, bedrock, caves, decorations, mobs, and structures. |
|
| `VoidGenerator.java` | Modern (1.17+) `ChunkGenerator` that produces empty chunks. Disables noise, surface, bedrock, caves, decorations, mobs, and structures. |
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,20 @@
|
||||||
|
|
||||||
## Bare-metal / VPS install
|
## Bare-metal / VPS install
|
||||||
|
|
||||||
1. Download `RackedLimbo-X.Y.Z.jar` from the
|
1. Download `LoginLimbo-X.Y.Z.jar` from the
|
||||||
[Releases page](https://github.com/s8n-ru/racked-limbo/releases/latest).
|
[Releases page](https://github.com/s8n-ru/login-limbo/releases/latest).
|
||||||
2. Drop the jar into your server's `plugins/` directory.
|
2. Drop the jar into your server's `plugins/` directory.
|
||||||
3. Restart the server. Do **not** use `/reload` — the listener wiring
|
3. Restart the server. Do **not** use `/reload` — the listener wiring
|
||||||
does not survive a hot reload.
|
does not survive a hot reload.
|
||||||
4. On first start the plugin will create:
|
4. On first start the plugin will create:
|
||||||
- `plugins/RackedLimbo/config.yml`
|
- `plugins/LoginLimbo/config.yml`
|
||||||
- The `auth_limbo/` world directory at the server data root.
|
- The `auth_limbo/` world directory at the server data root.
|
||||||
5. Tail the log and look for:
|
5. Tail the log and look for:
|
||||||
|
|
||||||
```
|
```
|
||||||
[RackedLimbo] Using AuthMe DB at: /path/to/plugins/AuthMe/authme.db
|
[LoginLimbo] Using AuthMe DB at: /path/to/plugins/AuthMe/authme.db
|
||||||
[RackedLimbo] Limbo world 'auth_limbo' already loaded.
|
[LoginLimbo] Limbo world 'auth_limbo' already loaded.
|
||||||
[RackedLimbo] Enabled. Listening for AuthMe LoginEvent at MONITOR.
|
[LoginLimbo] Enabled. Listening for AuthMe LoginEvent at MONITOR.
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see "No saved location for ..." on a successful `/login`, that
|
If you see "No saved location for ..." on a successful `/login`, that
|
||||||
|
|
@ -43,11 +43,11 @@ services:
|
||||||
TYPE: PAPER
|
TYPE: PAPER
|
||||||
VERSION: "1.21.11"
|
VERSION: "1.21.11"
|
||||||
PLUGINS: |
|
PLUGINS: |
|
||||||
https://github.com/s8n-ru/racked-limbo/releases/download/v1.0.0/RackedLimbo-1.0.0.jar
|
https://github.com/s8n-ru/login-limbo/releases/download/v1.0.0/LoginLimbo-1.0.0.jar
|
||||||
# Pin the version — itzg's auto-loader purges unrecognised jars on restart.
|
# Pin the version — itzg's auto-loader purges unrecognised jars on restart.
|
||||||
REMOVE_OLD_MODS: "TRUE"
|
REMOVE_OLD_MODS: "TRUE"
|
||||||
REMOVE_OLD_MODS_INCLUDE: "*.jar"
|
REMOVE_OLD_MODS_INCLUDE: "*.jar"
|
||||||
REMOVE_OLD_MODS_EXCLUDE: "RackedLimbo*.jar,AuthMe*.jar,(other-plugins)*.jar"
|
REMOVE_OLD_MODS_EXCLUDE: "LoginLimbo*.jar,AuthMe*.jar,(other-plugins)*.jar"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
ports:
|
ports:
|
||||||
|
|
@ -67,17 +67,17 @@ Notes:
|
||||||
Run as an op:
|
Run as an op:
|
||||||
|
|
||||||
```
|
```
|
||||||
/rackedlimbo
|
/loginlimbo
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
```
|
```
|
||||||
RackedLimbo 1.0.0 - sub: reload | tp <player>
|
LoginLimbo 1.0.0 - sub: reload | tp <player>
|
||||||
```
|
```
|
||||||
|
|
||||||
To confirm the DB read path is working, log out a test account, then run
|
To confirm the DB read path is working, log out a test account, then run
|
||||||
`/rackedlimbo tp <name>` — the plugin should report the world and
|
`/loginlimbo tp <name>` — the plugin should report the world and
|
||||||
coordinates it pulled from `authme.db`.
|
coordinates it pulled from `authme.db`.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
@ -92,7 +92,7 @@ be flagged in `CHANGELOG.md` for the affected version.
|
||||||
## Uninstalling
|
## Uninstalling
|
||||||
|
|
||||||
1. Stop the server.
|
1. Stop the server.
|
||||||
2. Delete `plugins/RackedLimbo-*.jar` and the `plugins/RackedLimbo/`
|
2. Delete `plugins/LoginLimbo-*.jar` and the `plugins/LoginLimbo/`
|
||||||
folder.
|
folder.
|
||||||
3. Optionally delete the `auth_limbo/` world directory if you no longer
|
3. Optionally delete the `auth_limbo/` world directory if you no longer
|
||||||
want the void world. AuthMe will fall back to whatever
|
want the void world. AuthMe will fall back to whatever
|
||||||
|
|
|
||||||
8
pom.xml
8
pom.xml
|
|
@ -4,12 +4,12 @@
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>ru.racked</groupId>
|
<groupId>ru.loginlimbo</groupId>
|
||||||
<artifactId>RackedLimbo</artifactId>
|
<artifactId>LoginLimbo</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>RackedLimbo</name>
|
<name>LoginLimbo</name>
|
||||||
<description>AuthMe-aware void limbo + reliable post-login teleport for Paper 1.21.11.</description>
|
<description>AuthMe-aware void limbo + reliable post-login teleport for Paper 1.21.11.</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
<relocations>
|
<relocations>
|
||||||
<relocation>
|
<relocation>
|
||||||
<pattern>org.sqlite</pattern>
|
<pattern>org.sqlite</pattern>
|
||||||
<shadedPattern>ru.racked.limbo.shaded.sqlite</shadedPattern>
|
<shadedPattern>ru.loginlimbo.shaded.sqlite</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
<filters>
|
<filters>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.racked.limbo;
|
package ru.loginlimbo;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
@ -27,21 +27,21 @@ import java.util.logging.Level;
|
||||||
*/
|
*/
|
||||||
public final class AuthMeDatabase {
|
public final class AuthMeDatabase {
|
||||||
|
|
||||||
private final RackedLimbo plugin;
|
private final LoginLimbo plugin;
|
||||||
private final File dbFile;
|
private final File dbFile;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
||||||
public AuthMeDatabase(RackedLimbo plugin, File dbFile) {
|
public AuthMeDatabase(LoginLimbo plugin, File dbFile) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.dbFile = dbFile;
|
this.dbFile = dbFile;
|
||||||
this.url = "jdbc:sqlite:" + dbFile.getAbsolutePath();
|
this.url = "jdbc:sqlite:" + dbFile.getAbsolutePath();
|
||||||
|
|
||||||
// Force-load shaded SQLite driver class so DriverManager finds it.
|
// Force-load shaded SQLite driver class so DriverManager finds it.
|
||||||
try {
|
try {
|
||||||
Class.forName("ru.racked.limbo.shaded.sqlite.JDBC");
|
Class.forName("ru.loginlimbo.shaded.sqlite.JDBC");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE,
|
plugin.getLogger().log(Level.SEVERE,
|
||||||
"[RackedLimbo] Shaded SQLite driver class missing — build is broken!", e);
|
"[LoginLimbo] Shaded SQLite driver class missing — build is broken!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ public final class AuthMeDatabase {
|
||||||
try (ResultSet rs = ps.executeQuery()) {
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
if (!rs.next()) {
|
if (!rs.next()) {
|
||||||
if (plugin.debug()) {
|
if (plugin.debug()) {
|
||||||
plugin.getLogger().info("[RackedLimbo][debug] No DB row for " + playerName);
|
plugin.getLogger().info("[LoginLimbo][debug] No DB row for " + playerName);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ public final class AuthMeDatabase {
|
||||||
if (worldName == null || worldName.isEmpty()) worldName = "world";
|
if (worldName == null || worldName.isEmpty()) worldName = "world";
|
||||||
World world = Bukkit.getWorld(worldName);
|
World world = Bukkit.getWorld(worldName);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
plugin.getLogger().warning("[RackedLimbo] World '" + worldName
|
plugin.getLogger().warning("[LoginLimbo] World '" + worldName
|
||||||
+ "' from authme.db is not loaded — cannot restore "
|
+ "' from authme.db is not loaded — cannot restore "
|
||||||
+ playerName + ".");
|
+ playerName + ".");
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -85,7 +85,7 @@ public final class AuthMeDatabase {
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().log(Level.WARNING,
|
plugin.getLogger().log(Level.WARNING,
|
||||||
"[RackedLimbo] Failed to read authme.db for " + playerName, e);
|
"[LoginLimbo] Failed to read authme.db for " + playerName, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.racked.limbo;
|
package ru.loginlimbo;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.GameRule;
|
import org.bukkit.GameRule;
|
||||||
|
|
@ -19,9 +19,9 @@ import org.bukkit.configuration.file.FileConfiguration;
|
||||||
*/
|
*/
|
||||||
public final class LimboWorldManager {
|
public final class LimboWorldManager {
|
||||||
|
|
||||||
private final RackedLimbo plugin;
|
private final LoginLimbo plugin;
|
||||||
|
|
||||||
public LimboWorldManager(RackedLimbo plugin) {
|
public LimboWorldManager(LoginLimbo plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,18 +31,18 @@ public final class LimboWorldManager {
|
||||||
|
|
||||||
World world = Bukkit.getWorld(name);
|
World world = Bukkit.getWorld(name);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
plugin.getLogger().info("[RackedLimbo] Creating limbo world '" + name + "'...");
|
plugin.getLogger().info("[LoginLimbo] Creating limbo world '" + name + "'...");
|
||||||
WorldCreator wc = new WorldCreator(name)
|
WorldCreator wc = new WorldCreator(name)
|
||||||
.environment(World.Environment.THE_END)
|
.environment(World.Environment.THE_END)
|
||||||
.generator(new VoidGenerator())
|
.generator(new VoidGenerator())
|
||||||
.generateStructures(false);
|
.generateStructures(false);
|
||||||
world = wc.createWorld();
|
world = wc.createWorld();
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
plugin.getLogger().severe("[RackedLimbo] Failed to create limbo world!");
|
plugin.getLogger().severe("[LoginLimbo] Failed to create limbo world!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLogger().info("[RackedLimbo] Limbo world '" + name + "' already loaded.");
|
plugin.getLogger().info("[LoginLimbo] Limbo world '" + name + "' already loaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
double sx = cfg.getDouble("limbo.spawn-x", 0.5);
|
double sx = cfg.getDouble("limbo.spawn-x", 0.5);
|
||||||
|
|
@ -73,7 +73,7 @@ public final class LimboWorldManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (built > 0) {
|
if (built > 0) {
|
||||||
plugin.getLogger().info("[RackedLimbo] Built " + built + " barrier blocks at "
|
plugin.getLogger().info("[LoginLimbo] Built " + built + " barrier blocks at "
|
||||||
+ cx + "," + py + "," + cz + ".");
|
+ cx + "," + py + "," + cz + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.racked.limbo;
|
package ru.loginlimbo;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
@ -11,7 +11,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RackedLimbo — companion plugin for AuthMe-ReReloaded.
|
* LoginLimbo — companion plugin for AuthMe-ReReloaded.
|
||||||
*
|
*
|
||||||
* Two responsibilities:
|
* Two responsibilities:
|
||||||
* 1. Provide a void {@code auth_limbo} world (no Multiverse required).
|
* 1. Provide a void {@code auth_limbo} world (no Multiverse required).
|
||||||
|
|
@ -21,14 +21,14 @@ import java.io.File;
|
||||||
*
|
*
|
||||||
* The DB is read directly via JDBC SQLite — we never modify it.
|
* The DB is read directly via JDBC SQLite — we never modify it.
|
||||||
*/
|
*/
|
||||||
public final class RackedLimbo extends JavaPlugin {
|
public final class LoginLimbo extends JavaPlugin {
|
||||||
|
|
||||||
private static RackedLimbo instance;
|
private static LoginLimbo instance;
|
||||||
private LimboWorldManager limboManager;
|
private LimboWorldManager limboManager;
|
||||||
private AuthMeDatabase authmeDb;
|
private AuthMeDatabase authmeDb;
|
||||||
private LoginListener loginListener;
|
private LoginListener loginListener;
|
||||||
|
|
||||||
public static RackedLimbo getInstance() {
|
public static LoginLimbo getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ public final class RackedLimbo extends JavaPlugin {
|
||||||
if (!dbFile.isAbsolute()) {
|
if (!dbFile.isAbsolute()) {
|
||||||
dbFile = new File(getServer().getWorldContainer(), dbPathStr);
|
dbFile = new File(getServer().getWorldContainer(), dbPathStr);
|
||||||
}
|
}
|
||||||
getLogger().info("[RackedLimbo] Using AuthMe DB at: " + dbFile.getAbsolutePath());
|
getLogger().info("[LoginLimbo] Using AuthMe DB at: " + dbFile.getAbsolutePath());
|
||||||
this.authmeDb = new AuthMeDatabase(this, dbFile);
|
this.authmeDb = new AuthMeDatabase(this, dbFile);
|
||||||
|
|
||||||
// Build the limbo world before AuthMe gets a chance to teleport players there.
|
// Build the limbo world before AuthMe gets a chance to teleport players there.
|
||||||
|
|
@ -56,13 +56,13 @@ public final class RackedLimbo extends JavaPlugin {
|
||||||
this.loginListener = new LoginListener(this, authmeDb);
|
this.loginListener = new LoginListener(this, authmeDb);
|
||||||
Bukkit.getPluginManager().registerEvents(loginListener, this);
|
Bukkit.getPluginManager().registerEvents(loginListener, this);
|
||||||
|
|
||||||
getLogger().info("[RackedLimbo] Enabled. Listening for AuthMe LoginEvent at MONITOR.");
|
getLogger().info("[LoginLimbo] Enabled. Listening for AuthMe LoginEvent at MONITOR.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
if (authmeDb != null) authmeDb.close();
|
if (authmeDb != null) authmeDb.close();
|
||||||
getLogger().info("[RackedLimbo] Disabled.");
|
getLogger().info("[LoginLimbo] Disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LimboWorldManager limbo() {
|
public LimboWorldManager limbo() {
|
||||||
|
|
@ -77,14 +77,14 @@ public final class RackedLimbo extends JavaPlugin {
|
||||||
return getConfig().getBoolean("debug", false);
|
return getConfig().getBoolean("debug", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- /rackedlimbo admin command ---------------- */
|
/* ---------------- /loginlimbo admin command ---------------- */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
if (!command.getName().equalsIgnoreCase("rackedlimbo")) return false;
|
if (!command.getName().equalsIgnoreCase("loginlimbo")) return false;
|
||||||
|
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
sender.sendMessage("RackedLimbo " + getDescription().getVersion()
|
sender.sendMessage("LoginLimbo " + getDescription().getVersion()
|
||||||
+ " — sub: reload | tp <player>");
|
+ " — sub: reload | tp <player>");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -92,12 +92,12 @@ public final class RackedLimbo extends JavaPlugin {
|
||||||
switch (args[0].toLowerCase()) {
|
switch (args[0].toLowerCase()) {
|
||||||
case "reload" -> {
|
case "reload" -> {
|
||||||
reloadConfig();
|
reloadConfig();
|
||||||
sender.sendMessage("[RackedLimbo] config reloaded.");
|
sender.sendMessage("[LoginLimbo] config reloaded.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case "tp" -> {
|
case "tp" -> {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
sender.sendMessage("Usage: /rackedlimbo tp <player>");
|
sender.sendMessage("Usage: /loginlimbo tp <player>");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Player target = Bukkit.getPlayerExact(args[1]);
|
Player target = Bukkit.getPlayerExact(args[1]);
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.racked.limbo;
|
package ru.loginlimbo;
|
||||||
|
|
||||||
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
|
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
|
||||||
import fr.xephi.authme.events.LoginEvent;
|
import fr.xephi.authme.events.LoginEvent;
|
||||||
|
|
@ -46,13 +46,13 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public final class LoginListener implements Listener {
|
public final class LoginListener implements Listener {
|
||||||
|
|
||||||
private final RackedLimbo plugin;
|
private final LoginLimbo plugin;
|
||||||
private final AuthMeDatabase db;
|
private final AuthMeDatabase db;
|
||||||
|
|
||||||
/** Tracks active plugin-chunk-tickets so we don't double-add or fail to release. */
|
/** Tracks active plugin-chunk-tickets so we don't double-add or fail to release. */
|
||||||
private final Set<String> activeTickets = new HashSet<>();
|
private final Set<String> activeTickets = new HashSet<>();
|
||||||
|
|
||||||
public LoginListener(RackedLimbo plugin, AuthMeDatabase db) {
|
public LoginListener(LoginLimbo plugin, AuthMeDatabase db) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
@ -80,11 +80,11 @@ public final class LoginListener implements Listener {
|
||||||
try {
|
try {
|
||||||
world.getChunkAt(cx, cz).addPluginChunkTicket(plugin);
|
world.getChunkAt(cx, cz).addPluginChunkTicket(plugin);
|
||||||
if (plugin.debug()) {
|
if (plugin.debug()) {
|
||||||
plugin.getLogger().info("[RackedLimbo][debug] Chunk-ticket added "
|
plugin.getLogger().info("[LoginLimbo][debug] Chunk-ticket added "
|
||||||
+ key + " for " + name);
|
+ key + " for " + name);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
plugin.getLogger().warning("[RackedLimbo] addPluginChunkTicket failed for "
|
plugin.getLogger().warning("[LoginLimbo] addPluginChunkTicket failed for "
|
||||||
+ name + ": " + t.getMessage());
|
+ name + ": " + t.getMessage());
|
||||||
activeTickets.remove(key);
|
activeTickets.remove(key);
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +102,7 @@ public final class LoginListener implements Listener {
|
||||||
|
|
||||||
final Location saved = db.getQuitLocation(name);
|
final Location saved = db.getQuitLocation(name);
|
||||||
if (saved == null) {
|
if (saved == null) {
|
||||||
plugin.getLogger().info("[RackedLimbo] No saved location for "
|
plugin.getLogger().info("[LoginLimbo] No saved location for "
|
||||||
+ name + " — leaving where AuthMe put them.");
|
+ name + " — leaving where AuthMe put them.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -116,19 +116,19 @@ public final class LoginListener implements Listener {
|
||||||
|
|
||||||
private void doTeleport(Player player, String name, Location saved) {
|
private void doTeleport(Player player, String name, Location saved) {
|
||||||
if (!player.isOnline()) {
|
if (!player.isOnline()) {
|
||||||
plugin.getLogger().info("[RackedLimbo] " + name
|
plugin.getLogger().info("[LoginLimbo] " + name
|
||||||
+ " went offline before restore — skipping.");
|
+ " went offline before restore — skipping.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
World world = saved.getWorld();
|
World world = saved.getWorld();
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
plugin.getLogger().warning("[RackedLimbo] Saved world for "
|
plugin.getLogger().warning("[LoginLimbo] Saved world for "
|
||||||
+ name + " is no longer loaded.");
|
+ name + " is no longer loaded.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.getLogger().info(String.format(
|
plugin.getLogger().info(String.format(
|
||||||
"[RackedLimbo] Restoring %s to %s(%.1f, %.1f, %.1f)",
|
"[LoginLimbo] Restoring %s to %s(%.1f, %.1f, %.1f)",
|
||||||
name, world.getName(), saved.getX(), saved.getY(), saved.getZ()));
|
name, world.getName(), saved.getX(), saved.getY(), saved.getZ()));
|
||||||
|
|
||||||
final int cx = saved.getBlockX() >> 4;
|
final int cx = saved.getBlockX() >> 4;
|
||||||
|
|
@ -151,10 +151,10 @@ public final class LoginListener implements Listener {
|
||||||
.thenAccept(success -> {
|
.thenAccept(success -> {
|
||||||
if (Boolean.TRUE.equals(success)) {
|
if (Boolean.TRUE.equals(success)) {
|
||||||
if (plugin.debug()) {
|
if (plugin.debug()) {
|
||||||
plugin.getLogger().info("[RackedLimbo][debug] Teleport ok for " + name);
|
plugin.getLogger().info("[LoginLimbo][debug] Teleport ok for " + name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLogger().warning("[RackedLimbo] teleportAsync returned false for "
|
plugin.getLogger().warning("[LoginLimbo] teleportAsync returned false for "
|
||||||
+ name + " — Paper may have rejected the location.");
|
+ name + " — Paper may have rejected the location.");
|
||||||
}
|
}
|
||||||
// Release the ticket 5s later — gives the client time to
|
// Release the ticket 5s later — gives the client time to
|
||||||
|
|
@ -162,13 +162,13 @@ public final class LoginListener implements Listener {
|
||||||
scheduleTicketRelease(world, cx, cz, key);
|
scheduleTicketRelease(world, cx, cz, key);
|
||||||
})
|
})
|
||||||
.exceptionally(ex -> {
|
.exceptionally(ex -> {
|
||||||
plugin.getLogger().warning("[RackedLimbo] teleportAsync threw for "
|
plugin.getLogger().warning("[LoginLimbo] teleportAsync threw for "
|
||||||
+ name + ": " + ex.getMessage());
|
+ name + ": " + ex.getMessage());
|
||||||
scheduleTicketRelease(world, cx, cz, key);
|
scheduleTicketRelease(world, cx, cz, key);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}).exceptionally(ex -> {
|
}).exceptionally(ex -> {
|
||||||
plugin.getLogger().warning("[RackedLimbo] getChunkAtAsyncUrgently threw for "
|
plugin.getLogger().warning("[LoginLimbo] getChunkAtAsyncUrgently threw for "
|
||||||
+ name + ": " + ex.getMessage());
|
+ name + ": " + ex.getMessage());
|
||||||
scheduleTicketRelease(world, cx, cz, key);
|
scheduleTicketRelease(world, cx, cz, key);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -185,7 +185,7 @@ public final class LoginListener implements Listener {
|
||||||
} finally {
|
} finally {
|
||||||
activeTickets.remove(key);
|
activeTickets.remove(key);
|
||||||
if (plugin.debug()) {
|
if (plugin.debug()) {
|
||||||
plugin.getLogger().info("[RackedLimbo][debug] Chunk-ticket released " + key);
|
plugin.getLogger().info("[LoginLimbo][debug] Chunk-ticket released " + key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 20L * 5L);
|
}, 20L * 5L);
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ru.racked.limbo;
|
package ru.loginlimbo;
|
||||||
|
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.generator.WorldInfo;
|
import org.bukkit.generator.WorldInfo;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# RackedLimbo configuration
|
# LoginLimbo configuration
|
||||||
|
|
||||||
# Limbo world settings — pre-auth players are kept here.
|
# Limbo world settings — pre-auth players are kept here.
|
||||||
limbo:
|
limbo:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name: RackedLimbo
|
name: LoginLimbo
|
||||||
main: ru.racked.limbo.RackedLimbo
|
main: ru.loginlimbo.LoginLimbo
|
||||||
version: ${project.version}
|
version: ${project.version}
|
||||||
api-version: '1.21'
|
api-version: '1.21'
|
||||||
authors: [s8n / racked.ru]
|
authors: [s8n / racked.ru]
|
||||||
|
|
@ -17,13 +17,13 @@ softdepend:
|
||||||
- VoidWorldGenerator
|
- VoidWorldGenerator
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
rackedlimbo:
|
loginlimbo:
|
||||||
description: RackedLimbo admin commands.
|
description: LoginLimbo admin commands.
|
||||||
aliases: [rlimbo]
|
aliases: [llimbo]
|
||||||
permission: rackedlimbo.admin
|
permission: loginlimbo.admin
|
||||||
usage: /rackedlimbo <reload|tp <player>>
|
usage: /loginlimbo <reload|tp <player>>
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
rackedlimbo.admin:
|
loginlimbo.admin:
|
||||||
description: Manage RackedLimbo at runtime.
|
description: Manage LoginLimbo at runtime.
|
||||||
default: op
|
default: op
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue