mirror of
https://gitlab.com/distant-horizons-team/distant-horizons-server-plugin.git
synced 2026-11-07 09:52:41 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73da993dc3 | ||
|
|
e3962a6965 | ||
|
|
709a7f20a1 | ||
|
|
fe4764042a | ||
|
|
c67e4b858c | ||
|
|
0831e8dd0b | ||
|
|
4e949b9547 | ||
|
|
2e069a7f14 | ||
|
|
f79f06b90e | ||
|
|
125fac4c39 | ||
|
|
e4988053d8 | ||
|
|
e6c1edd26e | ||
|
|
be8da0fd48 | ||
|
|
3688ef80c1 | ||
|
|
e3fe077f02 | ||
|
|
888fc62aa3 | ||
|
|
96de5a5e08 | ||
|
|
e73441d1a9 | ||
|
|
0d58b7abc7 | ||
|
|
4b9d21191e | ||
|
|
96323adf03 | ||
|
|
c5546107ca | ||
|
|
d84977998f | ||
|
|
b1b576f8e4 | ||
|
|
45612e36eb | ||
|
|
f9d327b6b9 | ||
|
|
30b82dbec4 | ||
|
|
246b37ee82 | ||
|
|
b9b0feb0f0 | ||
|
|
1530edf221 | ||
|
|
6004d56140 | ||
|
|
6b454927cb | ||
|
|
ce5ac6224a | ||
|
|
e39bec20e0 | ||
|
|
0825358a32 | ||
|
|
0ec9733384 | ||
|
|
9b5d68a120 | ||
|
|
b47cde87dd | ||
|
|
667694e5e7 | ||
|
|
c22665da86 | ||
|
|
7bcc35bf0a | ||
|
|
68a17c4e99 | ||
|
|
517bbe2ff2 | ||
|
|
a81b36960d | ||
|
|
456fdcdf91 | ||
|
|
24b8f65abf | ||
|
|
72a2758709 | ||
|
|
ce2837ebe6 | ||
|
|
aba5dc659c | ||
|
|
2a8cc8f2d3 | ||
|
|
107514dc20 | ||
|
|
e142d5b85d | ||
|
|
a348f2fb9b | ||
|
|
8410ea41b2 | ||
|
|
9e4f81d65a | ||
|
|
932908e6d4 | ||
|
|
7d5ff19bf2 | ||
|
|
b289b7e94f | ||
|
|
a79b5f3322 | ||
|
|
dab76ae2b7 | ||
|
|
c2b7a49492 | ||
|
|
c3c5362cfe | ||
|
|
061992d5b9 | ||
|
|
dcf2213625 | ||
|
|
5f1d243022 | ||
|
|
b00efd0798 | ||
|
|
44e4eff851 | ||
|
|
5265438b1c | ||
|
|
9baf68fb9e | ||
|
|
3c2abdcfc1 | ||
|
|
595e439934 | ||
|
|
3b5eb199b1 | ||
|
|
81f725267a | ||
|
|
1845c94f47 | ||
|
|
6e307ca594 | ||
|
|
a8a1bd12f5 | ||
|
|
eee2993010 | ||
|
|
e68e252c10 | ||
|
|
6d09fe674b | ||
|
|
0ea287332b | ||
|
|
c603e339b2 | ||
|
|
b3c98babcb | ||
|
|
248fa5be73 | ||
|
|
e7c30b7732 | ||
|
|
2ae0213bc8 | ||
|
|
191531d28e | ||
|
|
8e36adce9d | ||
|
|
79aac2a4b5 | ||
|
|
d102330432 | ||
|
|
48f0ded08f | ||
|
|
2eedc4a588 | ||
|
|
9accd512d0 | ||
|
|
96ef181f7b | ||
|
|
c6d69bd52a | ||
|
|
480f4ca0a3 | ||
|
|
6b45e447cc | ||
|
|
1bad8ceee8 | ||
|
|
b249c1ce90 | ||
|
|
dd1fc52fbe | ||
|
|
5591c5b7ec | ||
|
|
1f2f393803 | ||
|
|
c404cb3322 | ||
|
|
cb7ba37356 | ||
|
|
ce9bb1060c | ||
|
|
9402e6dee9 | ||
|
|
3b04ae153e | ||
|
|
f7f723b7c6 | ||
|
|
4e8cb7fdf2 | ||
|
|
3e49275c63 | ||
|
|
d349cd038e | ||
|
|
5452cab99a | ||
|
|
f22fb514b2 | ||
|
|
3e5fa994dc | ||
|
|
e5989134cc | ||
|
|
8c830e30ae | ||
|
|
aca9769658 | ||
|
|
6aa41955fe | ||
|
|
50730d0f11 | ||
|
|
2f4a070be8 | ||
|
|
0c76ae7748 | ||
|
|
b8b6bd1b84 | ||
|
|
49beaea384 | ||
|
|
1b71004a64 | ||
|
|
3e530589f4 | ||
|
|
43695a4c22 | ||
|
|
b57d40742f | ||
|
|
6e78561671 | ||
|
|
d48fe0934f | ||
|
|
7a2992d47c | ||
|
|
de03cad4f0 |
No files matched your search
@@ -1,13 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "io.netty:*"
|
||||
@@ -1,35 +0,0 @@
|
||||
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
pull_request:
|
||||
branches: [ "develop" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
|
||||
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
||||
- name: Update dependency graph
|
||||
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
|
||||
@@ -25,10 +25,13 @@ replay_pid*
|
||||
|
||||
# IntelliJ IDEA project config
|
||||
/.idea
|
||||
/*.iml
|
||||
|
||||
# Maven output
|
||||
/dependency-reduced-pom.xml
|
||||
/target
|
||||
/jars
|
||||
|
||||
# Test server
|
||||
/server
|
||||
/folia
|
||||
@@ -0,0 +1,49 @@
|
||||
include:
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
MAVEN_OPTS: >-
|
||||
-Dhttps.protocols=TLSv1.2
|
||||
-Dmaven.repo.local=.m2/repository
|
||||
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN
|
||||
-Dorg.slf4j.simpleLogger.showDateTime=true
|
||||
-Djava.awt.headless=true
|
||||
|
||||
MAVEN_CLI_OPTS: >-
|
||||
--batch-mode
|
||||
--errors
|
||||
--fail-at-end
|
||||
--show-version
|
||||
-DinstallAtEnd=true
|
||||
-DdeployAtEnd=true
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
default:
|
||||
image: maven:3.9-eclipse-temurin-21-alpine
|
||||
|
||||
cache:
|
||||
key: maven-repo-${CI_COMMIT_REF_SLUG}
|
||||
unprotect: true
|
||||
|
||||
paths:
|
||||
- .m2/repository
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
||||
parallel:
|
||||
matrix:
|
||||
- MC_VER: [ "1.16.5", "1.17.1", "1.18.2", "1.19.2", "1.19.4", "1.20.1", "1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", ]
|
||||
|
||||
script:
|
||||
- mvn package --projects pom-${MC_VER}.xml ${MAVEN_CLI_OPTS}
|
||||
- mv jars/*.jar ./
|
||||
|
||||
artifacts:
|
||||
name: DHS-${MC_VER}-${CI_COMMIT_REF_SLUG}-${CI_JOB_STARTED_AT}
|
||||
|
||||
paths:
|
||||
- "./*.jar"
|
||||
@@ -1,19 +1,23 @@
|
||||
# Server-side support for [Distant Horizons](https://gitlab.com/jeseibel/distant-horizons)
|
||||
<img src="https://gitlab.com/distant-horizons-team/distant-horizons-core/-/raw/main/_Misc%20Files/logo%20files/new/SVG/Distant-Horizons-Plugin.svg?ref_type=heads" height="128" alt="Plugin logo">
|
||||
|
||||
DH Support is a Bukkit/Spigot/Paper server plugin that transmits Level Of Detail (LOD) data to connected clients in order to provide a much higher view distance than Minecraft normally would allow. Distant Horizons _will_ work fine without this plugin, but then each client will have to be within normal view distance of chunks to load them, and they will not receive updates for distant chunks when they change.
|
||||
# Distant Horizons Server Plugin
|
||||
|
||||
DH Support is a Bukkit/Spigot/Paper/Folia server plugin that transmits Level Of Detail (LOD) data to players with Distant Horizons installed. Distant Horizons _will_ work fine without this plugin, but then each client will have to be within normal view distance of chunks to load them, and they will not receive updates for distant chunks when they change.
|
||||
|
||||
## Installation
|
||||
|
||||
Download the [latest release](https://github.com/Jckf/DH-Support/releases) and drop the JAR in your plugins folder, and you're done!
|
||||
Download the [latest release](https://gitlab.com/distant-horizons-team/distant-horizons-server-plugin/-/releases) and drop the JAR in your plugins folder, and you're done!
|
||||
|
||||
## Configuration
|
||||
|
||||
The default values should be pretty solid, but you may tweak them to better suit your specific needs. Everything you need to know should be in config.yml.
|
||||
|
||||
## Compilation
|
||||
## Building
|
||||
|
||||
The project uses Maven, so just run `mvn` in the project directory to compile and package a new JAR.
|
||||
|
||||
## Contribution
|
||||
|
||||
There are several ways to contribute to this project. You can offer your feedback on [the DH Discord](https://discord.gg/xAB8G4cENx) in [this thread](https://discord.com/channels/881614130614767666/1154490009735417989), report any issues or bugs you find, or attack an open issue and submit a pull request.
|
||||
There are several ways to contribute to this project. You can offer your feedback on [the DH Discord](https://discord.gg/xAB8G4cENx), report any issues or bugs you find, or attack an open issue and submit a pull request ❤️
|
||||
|
||||

|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.16</mc.api-version>
|
||||
<mc.api-version-patch>.5</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.17</mc.api-version>
|
||||
<mc.api-version-patch>.1</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.18</mc.api-version>
|
||||
<mc.api-version-patch>.2</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.19</mc.api-version>
|
||||
<mc.api-version-patch>.2</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.19</mc.api-version>
|
||||
<mc.api-version-patch>.4</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.20</mc.api-version>
|
||||
<mc.api-version-patch>.1</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.20</mc.api-version>
|
||||
<mc.api-version-patch>.2</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}${mc.api-version-patch}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.20</mc.api-version>
|
||||
<mc.api-version-patch>.4</mc.api-version-patch>
|
||||
<dhs.read-forge-byte>true</dhs.read-forge-byte>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}${mc.api-version-patch}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.20.6</mc.api-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.21.1</mc.api-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.21.3</mc.api-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>DHSupport-${mc.api-version}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>no.jckf</groupId>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>./pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skip-copy>false</skip-copy>
|
||||
<mc.api-version>1.21.4</mc.api-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${mc.api-version}-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -2,14 +2,19 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>no.jckf</groupId>
|
||||
<name>dh-support</name>
|
||||
<version>0.2.2</version>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
<version>${revision}</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<jdk>17</jdk>
|
||||
<revision>0.7.0</revision>
|
||||
<jdk>21</jdk>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<main.class>no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin</main.class>
|
||||
<mc.api-version></mc.api-version>
|
||||
<mc.api-version-patch></mc.api-version-patch>
|
||||
<dhs.read-forge-byte></dhs.read-forge-byte>
|
||||
<skip-copy>true</skip-copy>
|
||||
</properties>
|
||||
|
||||
<developers>
|
||||
@@ -30,48 +35,57 @@
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>jitpack</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.2.1-jre</version>
|
||||
<version>33.3.1-jre</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.1.0</version>
|
||||
<version>25.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lz4</groupId>
|
||||
<artifactId>lz4-java</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<groupId>org.tukaani</groupId>
|
||||
<artifactId>xz</artifactId>
|
||||
<version>1.10</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.technicallycoded</groupId>
|
||||
<artifactId>FoliaLib</artifactId>
|
||||
<version>v0.4.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -83,6 +97,19 @@
|
||||
<source>${jdk}</source>
|
||||
<target>${jdk}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
|
||||
<compilerArgs>
|
||||
<arg>-Xplugin:Manifold</arg>
|
||||
<arg>-AREAD_FORGE_BYTE=${dhs.read-forge-byte}</arg>
|
||||
</compilerArgs>
|
||||
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>systems.manifold</groupId>
|
||||
<artifactId>manifold-preprocessor</artifactId>
|
||||
<version>2024.1.34</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -92,18 +119,18 @@
|
||||
<version>3.6.0</version>
|
||||
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<shadedClassifierName>all</shadedClassifierName>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.jpountz</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.lz4</shadedPattern>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.bstats</shadedPattern>
|
||||
</relocation>
|
||||
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.bstats</shadedPattern>
|
||||
<pattern>com.tcoded.folialib</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.folialib</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
@@ -117,16 +144,57 @@
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>org.lz4</include>
|
||||
<include>org.tukaani</include>
|
||||
<include>org.bstats</include>
|
||||
<include>com.github.technicallycoded</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<outputDirectory>jars</outputDirectory>
|
||||
|
||||
<skip>${skip-copy}</skip>
|
||||
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>${project.artifactId}</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>${project.packaging}</type>
|
||||
<destFileName>DHS-${project.version}_for_MC-${mc.api-version}${mc.api-version-patch}.jar</destFileName>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
@@ -150,4 +218,19 @@
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<modules>
|
||||
<module>pom-1.16.5.xml</module>
|
||||
<module>pom-1.17.1.xml</module>
|
||||
<module>pom-1.18.2.xml</module>
|
||||
<module>pom-1.19.2.xml</module>
|
||||
<module>pom-1.19.4.xml</module>
|
||||
<module>pom-1.20.1.xml</module>
|
||||
<module>pom-1.20.2.xml</module>
|
||||
<module>pom-1.20.4.xml</module>
|
||||
<module>pom-1.20.6.xml</module>
|
||||
<module>pom-1.21.1.xml</module>
|
||||
<module>pom-1.21.3.xml</module>
|
||||
<module>pom-1.21.4.xml</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import com.tcoded.folialib.FoliaLib;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.scheduling.Scheduler;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BukkitScheduler implements Scheduler
|
||||
{
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
protected FoliaLib foliaLib;
|
||||
|
||||
protected Executor executor;
|
||||
|
||||
public BukkitScheduler(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
|
||||
this.foliaLib = new FoliaLib(this.plugin);
|
||||
|
||||
this.executor = Executors.newFixedThreadPool(this.plugin.getDhSupport().getConfig().getInt(DhsConfig.SCHEDULER_THREADS));
|
||||
|
||||
this.plugin.getDhSupport().info("Using " + Utils.ucFirst(this.foliaLib.getImplType().name().toLowerCase().replace('_', ' ')) + " scheduler.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReadWorldAsync()
|
||||
{
|
||||
return !this.foliaLib.isFolia();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnMainThread(Supplier<U> supplier)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
this.foliaLib.getScheduler().runNextTick((task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnRegionThread(UUID worldId, int x, int z, Supplier<U> supplier)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
Location location = new Location(
|
||||
this.plugin.getServer().getWorld(worldId),
|
||||
x,
|
||||
0,
|
||||
z
|
||||
);
|
||||
|
||||
this.foliaLib.getScheduler().runAtLocation(
|
||||
location,
|
||||
(task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnSeparateThread(Supplier<U> supplier)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public void runTimer(Runnable runnable, long initialDelay, long interval)
|
||||
{
|
||||
this.foliaLib.getScheduler().runTimer(runnable, initialDelay, interval);
|
||||
}
|
||||
}
|
||||
@@ -18,38 +18,208 @@
|
||||
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.WorldConfiguration;
|
||||
import no.jckf.dhsupport.core.dataobject.Beacon;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.BlockState;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class BukkitWorldInterface implements WorldInterface
|
||||
{
|
||||
protected static boolean BIOME_KEY_WARNING_SENT = false;
|
||||
|
||||
protected static boolean ASYNC_LOAD_WARNING_SENT = false;
|
||||
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
protected World world;
|
||||
|
||||
protected Block block;
|
||||
protected Configuration config;
|
||||
|
||||
public BukkitWorldInterface(World world)
|
||||
protected WorldConfiguration worldConfig;
|
||||
|
||||
protected Logger logger;
|
||||
|
||||
protected Map<String, ChunkSnapshot> chunks = new HashMap<>();
|
||||
|
||||
protected UnsafeValues unsafeValues;
|
||||
|
||||
@Nullable
|
||||
protected Method getBiomeKey;
|
||||
|
||||
@Nullable
|
||||
protected Method getChunkAtAsync;
|
||||
|
||||
public BukkitWorldInterface(DhSupportBukkitPlugin plugin, World world, Configuration config)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.world = world;
|
||||
this.config = config;
|
||||
this.worldConfig = new WorldConfiguration(this, config);
|
||||
}
|
||||
|
||||
public void setLogger(Logger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Logger getLogger()
|
||||
{
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
public void doUnsafeThings()
|
||||
{
|
||||
this.unsafeValues = Bukkit.getUnsafe();
|
||||
|
||||
// Detect if we're running under Paper (or a Paper fork) that has this patch:
|
||||
// https://github.com/PaperMC/Paper/commit/5bf259115c1ce29dd96df5fcf53739c94d39f902
|
||||
try {
|
||||
Class<?> regionAccessor = Class.forName("org.bukkit.RegionAccessor");
|
||||
|
||||
this.getBiomeKey = this.unsafeValues.getClass().getMethod("getBiomeKey", regionAccessor, int.class, int.class, int.class);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
if (!BIOME_KEY_WARNING_SENT) {
|
||||
this.getLogger().warning("Custom biomes are not supported on this server.");
|
||||
|
||||
BIOME_KEY_WARNING_SENT = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.getChunkAtAsync = this.world.getClass().getMethod("getChunkAtAsyncUrgently", int.class, int.class);
|
||||
} catch (NoSuchMethodException exception) {
|
||||
if (!ASYNC_LOAD_WARNING_SENT) {
|
||||
this.getLogger().warning("Async chunk loading is not supported on this server. Performance will suffer.");
|
||||
|
||||
ASYNC_LOAD_WARNING_SENT = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldInterface newInstance()
|
||||
{
|
||||
return new BukkitWorldInterface(this.world);
|
||||
BukkitWorldInterface newInstace = new BukkitWorldInterface(this.plugin, this.world, this.config);
|
||||
|
||||
newInstace.setLogger(this.getLogger());
|
||||
|
||||
newInstace.doUnsafeThings();
|
||||
|
||||
return newInstace;
|
||||
}
|
||||
|
||||
// Shitty cache :)
|
||||
protected Block getBlock(int x, int y, int z)
|
||||
protected ChunkSnapshot getChunk(int x, int z)
|
||||
{
|
||||
if (this.block == null || this.block.getY() != y || this.block.getX() != x || this.block.getZ() != z) {
|
||||
this.block = this.world.getBlockAt(x, y, z);
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
String key = chunkX + "x" + chunkZ;
|
||||
|
||||
if (this.chunks.containsKey(key)) {
|
||||
return this.chunks.get(key);
|
||||
}
|
||||
|
||||
return this.block;
|
||||
ChunkSnapshot chunk = this.world.getChunkAt(chunkX, chunkZ).getChunkSnapshot(true, true, false);
|
||||
|
||||
this.chunks.put(key, chunk);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId()
|
||||
{
|
||||
return this.world.getUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return this.world.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chunkExists(int x, int z)
|
||||
{
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
boolean alreadyLoaded = this.world.isChunkLoaded(chunkX, chunkZ);
|
||||
|
||||
if (alreadyLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int regionX = Coordinates.chunkToRegion(chunkX);
|
||||
int regionZ = Coordinates.chunkToRegion(chunkZ);
|
||||
|
||||
File regionFile = new File(world.getWorldFolder() + "/region/r." + regionX + "." + regionZ + ".mca");
|
||||
|
||||
if (!regionFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean exists = this.world.loadChunk(chunkX, chunkZ, false);
|
||||
|
||||
if (exists) {
|
||||
this.world.unloadChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadChunk(int x, int z)
|
||||
{
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
this.world.loadChunk(chunkX, chunkZ);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> loadChunkAsync(int x, int z)
|
||||
{
|
||||
if (this.getChunkAtAsync == null) {
|
||||
return this.plugin.getDhSupport().getScheduler().runOnMainThread(() -> this.loadChunk(x, z));
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
try {
|
||||
CompletableFuture<Chunk> chunkFuture = (CompletableFuture<Chunk>) this.getChunkAtAsync.invoke(this.world, chunkX, chunkZ);
|
||||
|
||||
return chunkFuture.thenApply((chunk) -> true);
|
||||
} catch (InvocationTargetException | IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unloadChunk(int x, int z)
|
||||
{
|
||||
return this.world.unloadChunk(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unloadChunkAsync(int x, int z)
|
||||
{
|
||||
return this.world.unloadChunkRequest(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,25 +243,36 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
@Override
|
||||
public int getHighestYAt(int x, int z)
|
||||
{
|
||||
return this.world.getHighestBlockAt(x, z).getY();
|
||||
return this.getChunk(x, z).getHighestBlockYAt(Coordinates.blockToChunkRelative(x), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiomeAt(int x, int z)
|
||||
{
|
||||
return this.getBlock(x, this.world.getSeaLevel(), z).getBiome().getKey().toString();
|
||||
NamespacedKey key = this.getChunk(x, z).getBiome(Coordinates.blockToChunkRelative(x), this.getSeaLevel(), Coordinates.blockToChunkRelative(z)).getKey();
|
||||
|
||||
// If the server just reports "custom" and we have access to getBiomeKey, try to get the correct biome name.
|
||||
if (key.toString().equals("minecraft:custom") && this.getBiomeKey != null) {
|
||||
try {
|
||||
key = (NamespacedKey) this.getBiomeKey.invoke(this.unsafeValues, this.world, x, this.getSeaLevel(), z);
|
||||
} catch (IllegalAccessException | InvocationTargetException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaterialAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getMaterial().getKey().toString();
|
||||
return this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getKey().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBlockStateAsStringAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getAsString();
|
||||
return this.getChunk(x, z).getBlockData(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,18 +302,43 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
@Override
|
||||
public byte getBlockLightAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getLightFromBlocks();
|
||||
return (byte) this.getChunk(x, z).getBlockEmittedLight(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getSkyLightAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getLightFromSky();
|
||||
return (byte) this.getChunk(x, z).getBlockSkyLight(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransparent(int x, int y, int z)
|
||||
public Collection<Beacon> getBeaconsInChunk(int x, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getMaterial().isTransparent();
|
||||
Collection<Beacon> beacons = new ArrayList<>();
|
||||
|
||||
BlockState[] blocks = this.world.getChunkAt(Coordinates.blockToChunk(x), Coordinates.blockToChunk(z)).getTileEntities();
|
||||
|
||||
for (BlockState block : blocks) {
|
||||
if (!block.getType().equals(Material.BEACON)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
beacons.add(
|
||||
new Beacon(
|
||||
block.getX(),
|
||||
block.getY(),
|
||||
block.getZ(),
|
||||
Color.WHITE.getRGB() // TODO?
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration getConfig()
|
||||
{
|
||||
return this.worldConfig;
|
||||
}
|
||||
}
|
||||
@@ -19,85 +19,93 @@
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.handler.ConfigLoader;
|
||||
import no.jckf.dhsupport.bukkit.handler.Handler;
|
||||
import no.jckf.dhsupport.bukkit.handler.PluginMessageProxy;
|
||||
import no.jckf.dhsupport.bukkit.handler.WorldHandler;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
{
|
||||
protected static int LOD_REFRESH_INTERVAL = 60 * 20;
|
||||
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected Metrics metrics;
|
||||
|
||||
protected final Map<Class<? extends Handler>, Handler> handlers = new HashMap<>()
|
||||
{{
|
||||
this.put(ConfigLoader.class, null);
|
||||
this.put(PluginMessageProxy.class, null);
|
||||
}};
|
||||
protected ConfigLoader configLoader;
|
||||
|
||||
protected PluginMessageProxy pluginMessageProxy;
|
||||
|
||||
protected BukkitScheduler scheduler;
|
||||
|
||||
@Override
|
||||
public void onEnable()
|
||||
{
|
||||
String pluginVersion = "N/A";
|
||||
String mcVersion = "N/A";
|
||||
|
||||
try {
|
||||
YamlConfiguration meta = new YamlConfiguration();
|
||||
meta.load(new InputStreamReader(this.getResource("plugin.yml")));
|
||||
|
||||
pluginVersion = meta.getString("version");
|
||||
mcVersion = meta.getString("mc-version");
|
||||
} catch (Exception exception) {
|
||||
|
||||
}
|
||||
|
||||
this.getLogger().info("I am DHS " + pluginVersion + " for MC " + mcVersion + ".");
|
||||
|
||||
this.dhSupport = new DhSupport();
|
||||
this.dhSupport.setLogger(this.getLogger());
|
||||
this.dhSupport.setDataDirectory(this.getDataFolder().getAbsolutePath());
|
||||
|
||||
this.metrics = new Metrics(this, 21843);
|
||||
|
||||
for (Class<? extends Handler> className : this.handlers.keySet()) {
|
||||
try {
|
||||
Handler instance = className
|
||||
.getConstructor(DhSupportBukkitPlugin.class)
|
||||
.newInstance(this);
|
||||
this.configLoader = new ConfigLoader(this);
|
||||
this.configLoader.onEnable();
|
||||
|
||||
instance.onEnable();
|
||||
|
||||
this.handlers.replace(className, instance);
|
||||
} catch (Exception exception) {
|
||||
this.getLogger().warning("Failed to enable handler: " + className);
|
||||
this.getLogger().warning(exception.getClass().getSimpleName() + " - " + exception.getMessage());
|
||||
this.getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.pluginMessageProxy = new PluginMessageProxy(this);
|
||||
this.pluginMessageProxy.onEnable();
|
||||
|
||||
this.dhSupport.onEnable();
|
||||
|
||||
this.scheduler = new BukkitScheduler(this);
|
||||
this.dhSupport.setScheduler(this.scheduler);
|
||||
|
||||
this.scheduler.runTimer(() -> {
|
||||
this.dhSupport.updateTouchedLods();
|
||||
}, LOD_REFRESH_INTERVAL, LOD_REFRESH_INTERVAL);
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(new WorldHandler(this), this);
|
||||
|
||||
this.getLogger().info("Ready 😀");
|
||||
this.getLogger().info("Ready!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
if (this.pluginMessageProxy != null) {
|
||||
this.pluginMessageProxy.onDisable();
|
||||
this.pluginMessageProxy = null;
|
||||
}
|
||||
|
||||
if (this.configLoader != null) {
|
||||
this.configLoader.onDisable();
|
||||
this.configLoader = null;
|
||||
}
|
||||
|
||||
if (this.dhSupport != null) {
|
||||
this.dhSupport.onDisable();
|
||||
this.dhSupport = null;
|
||||
}
|
||||
|
||||
for (Class<? extends Handler> className : this.handlers.keySet()) {
|
||||
Handler instance = this.handlers.get(className);
|
||||
|
||||
try {
|
||||
if (instance != null) {
|
||||
instance.onDisable();
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
this.getLogger().warning("Failed to disable handler: " + className);
|
||||
this.getLogger().warning(exception.getClass().getSimpleName() + " - " + exception.getMessage());
|
||||
}
|
||||
|
||||
this.handlers.replace(className, null);
|
||||
}
|
||||
|
||||
this.getLogger().info("👋😭");
|
||||
this.getLogger().info("Lights out!");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -20,6 +20,8 @@ package no.jckf.dhsupport.bukkit.handler;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
public class ConfigLoader extends Handler
|
||||
@@ -35,17 +37,34 @@ public class ConfigLoader extends Handler
|
||||
// Create config file if none is present.
|
||||
this.plugin.saveDefaultConfig();
|
||||
|
||||
// Bukkit plugin config.
|
||||
// The server's current config.
|
||||
FileConfiguration pluginConfig = this.plugin.getConfig();
|
||||
|
||||
// Load missing values from default file.
|
||||
pluginConfig.options().copyDefaults(true);
|
||||
// The plugin's default config.
|
||||
org.bukkit.configuration.Configuration defaultConfig = pluginConfig.getDefaults();
|
||||
|
||||
// Make sure nothing is returned for missing keys.
|
||||
pluginConfig.setDefaults(new MemoryConfiguration());
|
||||
|
||||
// Get the config version value from the default config file.
|
||||
Integer pluginConfigVersion = defaultConfig.getInt(DhsConfig.CONFIG_VERSION);
|
||||
|
||||
// Get the config version value from the server's config file.
|
||||
Integer serverConfigVersion = pluginConfig.getInt(DhsConfig.CONFIG_VERSION);
|
||||
|
||||
// If the two values differ, load defaults for missing values.
|
||||
if (!pluginConfigVersion.equals(serverConfigVersion)) {
|
||||
this.plugin.getLogger().warning("Your config file is for an older version of this plugin.");
|
||||
|
||||
pluginConfig.setDefaults(defaultConfig);
|
||||
pluginConfig.options().copyDefaults(true);
|
||||
}
|
||||
|
||||
// DH Support config.
|
||||
Configuration dhsConfig = this.plugin.getDhSupport().getConfig();
|
||||
|
||||
// Populate DH Support config.
|
||||
pluginConfig.getKeys(false).forEach((key) -> dhsConfig.set(key, pluginConfig.get(key)));
|
||||
pluginConfig.getKeys(true).forEach((key) -> dhsConfig.set(key, pluginConfig.get(key)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,11 +20,12 @@ package no.jckf.dhsupport.bukkit.handler;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.BukkitWorldInterface;
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
|
||||
@@ -41,7 +42,13 @@ public class WorldHandler implements Listener
|
||||
|
||||
protected void addWorldInterface(World world)
|
||||
{
|
||||
this.plugin.getDhSupport().setWorldInterface(world.getUID(), new BukkitWorldInterface(world));
|
||||
BukkitWorldInterface worldInterface = new BukkitWorldInterface(this.plugin, world, this.plugin.getDhSupport().getConfig());
|
||||
|
||||
worldInterface.setLogger(this.plugin.getDhSupport().getLogger());
|
||||
|
||||
worldInterface.doUnsafeThings();
|
||||
|
||||
this.plugin.getDhSupport().setWorldInterface(world.getUID(), worldInterface);
|
||||
}
|
||||
|
||||
protected void removeWorldInterface(World world)
|
||||
@@ -49,6 +56,11 @@ public class WorldHandler implements Listener
|
||||
this.plugin.getDhSupport().setWorldInterface(world.getUID(), null);
|
||||
}
|
||||
|
||||
protected void touchLod(Location location)
|
||||
{
|
||||
this.plugin.getDhSupport().touchLod(location.getWorld().getUID(), location.getBlockX(), location.getBlockZ());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent worldLoad)
|
||||
{
|
||||
@@ -62,14 +74,14 @@ public class WorldHandler implements Listener
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkLoad(ChunkLoadEvent chunkLoad)
|
||||
public void onBlockPlace(BlockPlaceEvent blockPlace)
|
||||
{
|
||||
|
||||
this.touchLod(blockPlace.getBlock().getLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkUnload(ChunkUnloadEvent chunkUnload)
|
||||
public void onBlockBreak(BlockBreakEvent blockBreak)
|
||||
{
|
||||
|
||||
this.touchLod(blockBreak.getBlock().getLocation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
// Block -> chunk -> section -> region
|
||||
public class Coordinates
|
||||
{
|
||||
protected static final int BLOCK_TO_CHUNK_POWER = 4;
|
||||
|
||||
protected static final int CHUNK_TO_SECTION_POWER = 2;
|
||||
|
||||
protected static final int SECTION_TO_REGION_POWER = 4;
|
||||
|
||||
// UP
|
||||
|
||||
public static int blockToChunk(int value)
|
||||
{
|
||||
return value >> BLOCK_TO_CHUNK_POWER;
|
||||
}
|
||||
|
||||
public static int chunkToSection(int value)
|
||||
{
|
||||
return value >> CHUNK_TO_SECTION_POWER;
|
||||
}
|
||||
|
||||
public static int sectionToRegion(int value)
|
||||
{
|
||||
return value >> SECTION_TO_REGION_POWER;
|
||||
}
|
||||
|
||||
// DOWN
|
||||
|
||||
public static int regionToSection(int value)
|
||||
{
|
||||
return value << SECTION_TO_REGION_POWER;
|
||||
}
|
||||
|
||||
public static int sectionToChunk(int value)
|
||||
{
|
||||
return value << CHUNK_TO_SECTION_POWER;
|
||||
}
|
||||
|
||||
public static int chunkToBlock(int value)
|
||||
{
|
||||
return value << BLOCK_TO_CHUNK_POWER;
|
||||
}
|
||||
|
||||
// UP VIA
|
||||
|
||||
public static int blockToSection(int value)
|
||||
{
|
||||
return value >> (BLOCK_TO_CHUNK_POWER + CHUNK_TO_SECTION_POWER);
|
||||
}
|
||||
|
||||
public static int blockToRegion(int value)
|
||||
{
|
||||
return value >> (BLOCK_TO_CHUNK_POWER + CHUNK_TO_SECTION_POWER + SECTION_TO_REGION_POWER);
|
||||
}
|
||||
|
||||
public static int chunkToRegion(int value)
|
||||
{
|
||||
return value >> (CHUNK_TO_SECTION_POWER + SECTION_TO_REGION_POWER);
|
||||
}
|
||||
|
||||
// DOWN VIA
|
||||
|
||||
public static int regionToChunk(int value)
|
||||
{
|
||||
return value << (CHUNK_TO_SECTION_POWER + SECTION_TO_REGION_POWER);
|
||||
}
|
||||
|
||||
public static int regionToBlock(int value)
|
||||
{
|
||||
return value << (BLOCK_TO_CHUNK_POWER + CHUNK_TO_SECTION_POWER + SECTION_TO_REGION_POWER);
|
||||
}
|
||||
|
||||
public static int sectionToBlock(int value)
|
||||
{
|
||||
return value << (BLOCK_TO_CHUNK_POWER + CHUNK_TO_SECTION_POWER);
|
||||
}
|
||||
|
||||
// RELATIVE
|
||||
|
||||
public static int blockToChunkRelative(int value)
|
||||
{
|
||||
return value & 0x0F;
|
||||
}
|
||||
}
|
||||
@@ -21,48 +21,78 @@ package no.jckf.dhsupport.core;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.configuration.Configurable;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
import no.jckf.dhsupport.core.database.migrations.CreateLodsTable;
|
||||
import no.jckf.dhsupport.core.database.models.LodModel;
|
||||
import no.jckf.dhsupport.core.database.repositories.AsyncLodRepository;
|
||||
import no.jckf.dhsupport.core.dataobject.Beacon;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.handler.LodHandler;
|
||||
import no.jckf.dhsupport.core.handler.PlayerConfigHandler;
|
||||
import no.jckf.dhsupport.core.handler.PluginMessageHandler;
|
||||
import no.jckf.dhsupport.core.lodbuilders.LodBuilder;
|
||||
import no.jckf.dhsupport.core.message.plugin.PluginMessageSender;
|
||||
import no.jckf.dhsupport.core.scheduling.Scheduler;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class DhSupport implements Configurable
|
||||
{
|
||||
protected String dataDirectory;
|
||||
|
||||
protected Database database;
|
||||
|
||||
protected AsyncLodRepository lodRepository;
|
||||
|
||||
protected Configuration configuration;
|
||||
|
||||
protected Logger logger;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
|
||||
protected Map<UUID, WorldInterface> worldInterfaces = new HashMap<>();
|
||||
|
||||
protected PluginMessageHandler pluginMessageHandler;
|
||||
|
||||
protected PluginMessageSender pluginMessageSender;
|
||||
|
||||
protected ExecutorService executor;
|
||||
protected Map<String, CompletableFuture<Lod>> queuedBuilders = new HashMap<>();
|
||||
|
||||
protected Map<UUID, Map<String, byte[]>> lodCache = new HashMap<>();
|
||||
protected Map<String, LodModel> touchedLods = new ConcurrentHashMap<>();
|
||||
|
||||
public DhSupport()
|
||||
{
|
||||
// Mumble mumble. Something about passing references to an incomplete "this".
|
||||
this.configuration = new Configuration();
|
||||
|
||||
this.database = new Database();
|
||||
this.lodRepository = new AsyncLodRepository(this.database);
|
||||
|
||||
this.pluginMessageHandler = new PluginMessageHandler(this);
|
||||
}
|
||||
|
||||
public void onEnable()
|
||||
{
|
||||
this.executor = Executors.newFixedThreadPool(4);
|
||||
this.lodRepository.setLogger(this.getLogger());
|
||||
|
||||
try {
|
||||
this.database.open(this.getDataDirectory() + "/data.sqlite");
|
||||
|
||||
this.database.addMigration(CreateLodsTable.class);
|
||||
|
||||
this.database.migrate();
|
||||
} catch (Exception exception) {
|
||||
this.warning("Failed to initialize database: " + exception.getMessage());
|
||||
// TODO: Disable the plugin?
|
||||
}
|
||||
|
||||
(new PlayerConfigHandler(this, this.pluginMessageHandler)).register();
|
||||
(new LodHandler(this, this.pluginMessageHandler)).register();
|
||||
@@ -77,20 +107,34 @@ public class DhSupport implements Configurable
|
||||
}
|
||||
}
|
||||
|
||||
public void setDataDirectory(String dataDirectory)
|
||||
{
|
||||
this.dataDirectory = dataDirectory;
|
||||
}
|
||||
|
||||
public String getDataDirectory()
|
||||
{
|
||||
return this.dataDirectory;
|
||||
}
|
||||
|
||||
public void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
return this.scheduler;
|
||||
}
|
||||
|
||||
public void setWorldInterface(UUID id, @Nullable WorldInterface worldInterface)
|
||||
{
|
||||
if (worldInterface == null) {
|
||||
//this.info("Removing world interface for " + id);
|
||||
|
||||
this.worldInterfaces.remove(id);
|
||||
this.lodCache.remove(id);
|
||||
return;
|
||||
}
|
||||
|
||||
//this.info("Adding world interface for " + id);
|
||||
|
||||
this.worldInterfaces.put(id, worldInterface);
|
||||
this.lodCache.put(id, new HashMap<>());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -99,7 +143,6 @@ public class DhSupport implements Configurable
|
||||
return this.worldInterfaces.get(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PluginMessageHandler getPluginMessageHandler()
|
||||
{
|
||||
return this.pluginMessageHandler;
|
||||
@@ -116,13 +159,13 @@ public class DhSupport implements Configurable
|
||||
return this.pluginMessageSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncLodRepository getLodRepository()
|
||||
{
|
||||
return this.lodRepository;
|
||||
}
|
||||
|
||||
public Configuration getConfig()
|
||||
{
|
||||
if (this.configuration == null) {
|
||||
this.configuration = new Configuration();
|
||||
}
|
||||
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
@@ -147,33 +190,188 @@ public class DhSupport implements Configurable
|
||||
this.getLogger().warning(message);
|
||||
}
|
||||
|
||||
public CompletableFuture<Lod> queueBuilder(LodBuilder builder)
|
||||
public LodBuilder getBuilder(WorldInterface world, SectionPosition position)
|
||||
{
|
||||
return CompletableFuture.supplyAsync(builder::generate, this.executor);
|
||||
}
|
||||
String builderType = world.getConfig().getString(DhsConfig.BUILDER_TYPE);
|
||||
|
||||
public CompletableFuture<byte[]> getLodData(UUID worldId, SectionPosition position)
|
||||
{
|
||||
String key = position.getX() + "x" + position.getZ();
|
||||
LodBuilder builder;
|
||||
|
||||
if (!this.lodCache.get(worldId).containsKey(key)) {
|
||||
//this.info("Cache miss: " + worldId + " " + key);
|
||||
try {
|
||||
Class<? extends LodBuilder> builderClass = Class.forName(LodBuilder.class.getPackageName() + "." + builderType).asSubclass(LodBuilder.class);
|
||||
|
||||
LodBuilder builder = new LodBuilder(this.getWorldInterface(worldId).newInstance(), position);
|
||||
Constructor<?> constructor = builderClass.getConstructor(WorldInterface.class, SectionPosition.class);
|
||||
|
||||
return this.queueBuilder(builder).thenApply((lod) -> {
|
||||
Encoder encoder = new Encoder();
|
||||
lod.encode(encoder);
|
||||
byte[] data = encoder.toByteArray();
|
||||
|
||||
this.lodCache.get(worldId).put(key, data);
|
||||
|
||||
return data;
|
||||
});
|
||||
builder = (LodBuilder) constructor.newInstance(world, position);
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | ClassNotFoundException exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//this.info("Cache hit: " + worldId + " " + key);
|
||||
return builder;
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(this.lodCache.get(worldId).get(key));
|
||||
public CompletableFuture<Lod> queueBuilder(UUID worldId, SectionPosition position, LodBuilder builder)
|
||||
{
|
||||
String key = LodModel.create()
|
||||
.setWorldId(worldId)
|
||||
.setX(position.getX())
|
||||
.setZ(position.getZ())
|
||||
.toString();
|
||||
|
||||
if (this.queuedBuilders.containsKey(key)) {
|
||||
return this.queuedBuilders.get(key);
|
||||
}
|
||||
|
||||
Scheduler scheduler = this.getScheduler();
|
||||
|
||||
CompletableFuture<Lod> queued;
|
||||
|
||||
// TODO: Fetch chunk data on region thread, then move to builder thread.
|
||||
if (scheduler.canReadWorldAsync()) {
|
||||
queued = this.getScheduler().runOnSeparateThread(
|
||||
builder::generate
|
||||
);
|
||||
} else {
|
||||
queued = this.getScheduler().runOnRegionThread(
|
||||
worldId,
|
||||
Coordinates.sectionToBlock(position.getX()),
|
||||
Coordinates.sectionToBlock(position.getZ()),
|
||||
builder::generate
|
||||
);
|
||||
}
|
||||
|
||||
queued = queued.thenApply((lod) -> {
|
||||
this.queuedBuilders.remove(key);
|
||||
|
||||
return lod;
|
||||
})
|
||||
.exceptionally((exception) -> {
|
||||
exception.printStackTrace();
|
||||
|
||||
this.queuedBuilders.remove(key);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
this.queuedBuilders.put(key, queued);
|
||||
|
||||
return queued;
|
||||
}
|
||||
|
||||
public CompletableFuture<LodModel> getLod(UUID worldId, SectionPosition position)
|
||||
{
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
return this.getLodRepository()
|
||||
.loadLodAsync(worldId, position.getX(), position.getZ())
|
||||
.thenCompose((modelFromDb) -> {
|
||||
// If a LOD was found in the database, return it.
|
||||
if (modelFromDb != null) {
|
||||
return CompletableFuture.completedFuture(modelFromDb);
|
||||
}
|
||||
|
||||
WorldInterface world = this.getWorldInterface(worldId).newInstance();
|
||||
|
||||
List<CompletableFuture<Boolean>> loads = new ArrayList<>();
|
||||
|
||||
// Load all the chunks we need for this request.
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplayer = 0; zMultiplayer < 4; zMultiplayer++) {
|
||||
loads.add(world.loadChunkAsync(worldX + 16 * xMultiplier, worldZ + 16 * zMultiplayer));
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for chunk loads, then...
|
||||
return CompletableFuture.allOf(loads.toArray(new CompletableFuture[loads.size()]))
|
||||
.thenCompose((asd) -> {
|
||||
// No LOD was found. Start building a new one.
|
||||
CompletableFuture<Lod> lodFuture = this.queueBuilder(worldId, position, this.getBuilder(world, position));
|
||||
|
||||
// Find any beacons that should appear in this LOD.
|
||||
CompletableFuture<Collection<Beacon>> beaconFuture = this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
Collection<Beacon> accumulator = new ArrayList<>();
|
||||
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplayer = 0; zMultiplayer < 4; zMultiplayer++) {
|
||||
accumulator.addAll(world.getBeaconsInChunk(worldX + 16 * xMultiplier, worldZ + 16 * zMultiplayer));
|
||||
}
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
});
|
||||
|
||||
// Combine the LOD and beacons and save the result in the database.
|
||||
return lodFuture.thenCombine(beaconFuture, (lod, beacons) -> {
|
||||
// Ask for all the chunks in the area to be unloaded.
|
||||
// It's unlikely that they all should be, but we'll leave that up to the server.
|
||||
this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplayer = 0; zMultiplayer < 4; zMultiplayer++) {
|
||||
world.unloadChunkAsync(worldX + 16 * xMultiplier, worldZ + 16 * zMultiplayer);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
Encoder lodEncoder = new Encoder();
|
||||
lod.encode(lodEncoder);
|
||||
|
||||
Encoder beaconEncoder = new Encoder();
|
||||
beaconEncoder.writeCollection(beacons);
|
||||
|
||||
return this.lodRepository.saveLodAsync(
|
||||
worldId,
|
||||
position.getX(),
|
||||
position.getZ(),
|
||||
lodEncoder.toByteArray(),
|
||||
beaconEncoder.toByteArray()
|
||||
);
|
||||
})
|
||||
.thenCompose((f) -> f); // Unwrap the nested future.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void touchLod(UUID worldId, int x, int z)
|
||||
{
|
||||
int sectionX = Coordinates.blockToSection(x);
|
||||
int sectionZ = Coordinates.blockToSection(z);
|
||||
|
||||
LodModel lodModel = LodModel.create()
|
||||
.setWorldId(worldId)
|
||||
.setX(sectionX)
|
||||
.setZ(sectionZ);
|
||||
|
||||
String key = lodModel.toString();
|
||||
|
||||
if (this.touchedLods.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.touchedLods.put(key, lodModel);
|
||||
}
|
||||
|
||||
public void updateTouchedLods()
|
||||
{
|
||||
for (String key : this.touchedLods.keySet()) {
|
||||
LodModel lodModel = this.touchedLods.get(key);
|
||||
|
||||
this.getLodRepository().deleteLodAsync(lodModel.getWorldId(), lodModel.getX(), lodModel.getZ())
|
||||
.thenAccept((deleted) -> {
|
||||
if (!deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
SectionPosition position = new SectionPosition();
|
||||
position.setDetailLevel(6);
|
||||
position.setX(lodModel.getX());
|
||||
position.setZ(lodModel.getZ());
|
||||
|
||||
this.getLod(lodModel.getWorldId(), position);
|
||||
|
||||
this.touchedLods.remove(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
import no.jckf.dhsupport.core.dataobject.DataPoint;
|
||||
import no.jckf.dhsupport.core.dataobject.IdMapping;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LodBuilder
|
||||
{
|
||||
protected WorldInterface worldInterface;
|
||||
|
||||
protected SectionPosition position;
|
||||
|
||||
public LodBuilder(WorldInterface worldInterface, SectionPosition position)
|
||||
{
|
||||
this.worldInterface = worldInterface;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Lod generate()
|
||||
{
|
||||
int minY = this.worldInterface.getMinY();
|
||||
int maxY = this.worldInterface.getMaxY();
|
||||
int height = maxY - minY;
|
||||
|
||||
int seaLevel = this.worldInterface.getSeaLevel();
|
||||
int relativeSeaLevel = seaLevel - minY;
|
||||
|
||||
int offsetX = this.position.getX() * 64;
|
||||
int offsetZ = this.position.getZ() * 64;
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>();
|
||||
|
||||
String biome = ""; // Initialize to squelch warnings.
|
||||
|
||||
for (int relativeX = 0; relativeX < Lod.width; relativeX++) {
|
||||
for (int relativeZ = 0; relativeZ < Lod.width; relativeZ++) {
|
||||
int worldX = offsetX + relativeX;
|
||||
int worldZ = offsetZ + relativeZ;
|
||||
|
||||
// Actual Y of top-most block.
|
||||
int topLayer = this.worldInterface.getHighestYAt(worldX, worldZ);
|
||||
|
||||
// Distance from bottom to top-most block.
|
||||
int relativeTopLayer = topLayer - minY;
|
||||
|
||||
biome = this.worldInterface.getBiomeAt(offsetX + relativeX, offsetZ + relativeZ);
|
||||
|
||||
List<DataPoint> column = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
DataPoint previous = null;
|
||||
|
||||
@Nullable
|
||||
Integer solidGround = null;
|
||||
|
||||
for (int relativeY = height; (solidGround == null || relativeY >= solidGround) && relativeY >= 0; relativeY--) {
|
||||
int worldY = minY + relativeY;
|
||||
|
||||
String material = this.worldInterface.getMaterialAt(worldX, worldY, worldZ);
|
||||
|
||||
if (solidGround == null) {
|
||||
switch (material) {
|
||||
case "minecraft:stone":
|
||||
case "minecraft:grass":
|
||||
case "minecraft:dirt":
|
||||
case "minecraft:sand":
|
||||
case "minecraft:sandstone":
|
||||
case "minecraft:mycelium":
|
||||
solidGround = Math.min(relativeY - 10, relativeSeaLevel - 10);
|
||||
}
|
||||
}
|
||||
|
||||
String compositeKey = biome + "|" + material;
|
||||
//String compositeKey = biome + "|" + material + "|" + this.worldInterface.getBlockStateAsStringAt(worldX, worldY, worldZ);
|
||||
|
||||
@Nullable
|
||||
Integer id = mapMap.get(compositeKey);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, null));
|
||||
//idMappings.add(new IdMapping(biome, material, this.worldInterface.getBlockPropertiesAt(worldX, worldY, worldZ)));
|
||||
id = idMappings.size() - 1;
|
||||
mapMap.put(compositeKey, id);
|
||||
}
|
||||
|
||||
DataPoint point;
|
||||
|
||||
if (previous != null && previous.getMappingId() == id) {
|
||||
point = previous;
|
||||
|
||||
point.setStartY(point.getStartY() - 1);
|
||||
point.setHeight(point.getHeight() + 1);
|
||||
} else {
|
||||
point = new DataPoint();
|
||||
column.add(point);
|
||||
|
||||
point.setStartY(relativeY);
|
||||
point.setMappingId(id);
|
||||
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, worldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, worldY + 1, worldZ));
|
||||
|
||||
if (relativeY == height && (material.equals("minecraft:air") || material.equals("minecraft:void_air"))) {
|
||||
point.setStartY(relativeTopLayer + 1);
|
||||
point.setHeight(height - relativeTopLayer);
|
||||
|
||||
relativeY = point.getStartY();
|
||||
}
|
||||
}
|
||||
|
||||
previous = point;
|
||||
}
|
||||
|
||||
columns.add(column);
|
||||
}
|
||||
}
|
||||
|
||||
return new Lod(this.worldInterface, this.position, idMappings, columns);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
@@ -34,4 +35,18 @@ public class Utils
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static String ucFirst(String string, Locale locale)
|
||||
{
|
||||
if (string.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return string.substring(0, 1).toUpperCase(locale) + string.substring(1);
|
||||
}
|
||||
|
||||
public static String ucFirst(String string)
|
||||
{
|
||||
return ucFirst(string, Locale.getDefault());
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package no.jckf.dhsupport.core.configuration;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -25,28 +26,53 @@ public class Configuration
|
||||
{
|
||||
protected Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
public void set(String key, Object value)
|
||||
public void set(String key, @Nullable Object value)
|
||||
{
|
||||
this.variables.put(key, value);
|
||||
}
|
||||
|
||||
public Object get(String key)
|
||||
public void unset(String key)
|
||||
{
|
||||
return this.variables.get(key);
|
||||
this.variables.remove(key);
|
||||
}
|
||||
|
||||
public boolean getBool(String key)
|
||||
public @Nullable Object get(String key, @Nullable Object defaultValue)
|
||||
{
|
||||
return (boolean) this.get(key);
|
||||
return this.variables.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
public Integer getInt(String key)
|
||||
public @Nullable Object get(String key)
|
||||
{
|
||||
return (Integer) this.get(key);
|
||||
return this.get(key, null);
|
||||
}
|
||||
|
||||
public String getString(String key)
|
||||
public @Nullable Boolean getBool(String key, @Nullable Boolean defaultValue)
|
||||
{
|
||||
return (String) this.get(key);
|
||||
return (Boolean) this.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public @Nullable Boolean getBool(String key)
|
||||
{
|
||||
return this.getBool(key, null);
|
||||
}
|
||||
|
||||
public @Nullable Integer getInt(String key, @Nullable Integer defaultValue)
|
||||
{
|
||||
return (Integer) this.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public @Nullable Integer getInt(String key)
|
||||
{
|
||||
return this.getInt(key, null);
|
||||
}
|
||||
|
||||
public @Nullable String getString(String key, @Nullable String defaultValue)
|
||||
{
|
||||
return (String) this.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public @Nullable String getString(String key)
|
||||
{
|
||||
return (String) this.get(key, null);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,10 @@
|
||||
|
||||
package no.jckf.dhsupport.core.configuration;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DhsConfig
|
||||
{
|
||||
public static String CONFIG_VERSION = "config_version";
|
||||
|
||||
public static String RENDER_DISTANCE = "render_distance";
|
||||
|
||||
public static String DISTANT_GENERATION_ENABLED = "distant_generation_enabled";
|
||||
@@ -34,24 +30,31 @@ public abstract class DhsConfig
|
||||
|
||||
public static String REAL_TIME_UPDATES_ENABLED = "real_time_updates_enabled";
|
||||
|
||||
public static String REAL_TIME_UPDATE_RADIUS = "real_time_update_radius";
|
||||
|
||||
public static String LOGIN_DATA_SYNC_ENABLED = "login_data_sync_enabled";
|
||||
|
||||
public static String LOGIN_DATA_SYNC_RADIUS = "login_data_sync_radius";
|
||||
|
||||
public static String LOGIN_DATA_SYNC_RC_LIMIT = "login_data_sync_rc_limit";
|
||||
|
||||
public static Collection<String> getKeys()
|
||||
{
|
||||
List<String> keys = new ArrayList<>();
|
||||
public static String MAX_DATA_TRANSFER_SPEED = "max_data_transfer_speed";
|
||||
|
||||
Arrays.stream(DhsConfig.class.getDeclaredFields())
|
||||
.filter((field) -> Modifier.isStatic(field.getModifiers()))
|
||||
.forEach((field) -> {
|
||||
try {
|
||||
keys.add((String) field.get(DhsConfig.class));
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
public static String SCHEDULER_THREADS = "scheduler_threads";
|
||||
|
||||
return keys;
|
||||
}
|
||||
public static String GENERATE_NEW_CHUNKS = "generate_new_chunks";
|
||||
|
||||
public static String LEVEL_KEY_PREFIX = "level_key_prefix";
|
||||
|
||||
public static String BORDER_CENTER = "border_center";
|
||||
|
||||
public static String BORDER_RADIUS = "border_radius";
|
||||
|
||||
public static String BUILDER_TYPE = "builder_type";
|
||||
|
||||
public static String BUILDER_RESOLUTION = "builder_resolution";
|
||||
|
||||
public static String SCAN_TO_SEA_LEVEL= "scan_to_sea_level";
|
||||
|
||||
public static String INCLUDE_NON_COLLIDING_TOP_LAYER = "include_non-colliding_top_layer";
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package no.jckf.dhsupport.core.configuration;
|
||||
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class WorldConfiguration extends Configuration
|
||||
{
|
||||
protected static String WORLD_PREFIX = "worlds.%s.";
|
||||
|
||||
protected WorldInterface world;
|
||||
|
||||
protected Configuration config;
|
||||
|
||||
public WorldConfiguration(WorldInterface world, Configuration config)
|
||||
{
|
||||
this.world = world;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable Object value)
|
||||
{
|
||||
this.config.set(WORLD_PREFIX.formatted(this.world.getName()) + key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unset(String key)
|
||||
{
|
||||
this.config.unset(WORLD_PREFIX.formatted(this.world.getName()) + key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object get(String key, @Nullable Object defaultValue)
|
||||
{
|
||||
Object specific = this.config.get(WORLD_PREFIX.formatted(this.world.getName()) + key);
|
||||
|
||||
return specific == null ? this.config.get(key, defaultValue) : specific;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database;
|
||||
|
||||
import no.jckf.dhsupport.core.database.migrations.Migration;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Database
|
||||
{
|
||||
protected String path;
|
||||
|
||||
protected Connection connection;
|
||||
|
||||
protected Map<String, Class<? extends Migration>> migrations = new HashMap<>();
|
||||
|
||||
public Connection getConnection() throws SQLException
|
||||
{
|
||||
if (this.connection == null || this.connection.isClosed()) {
|
||||
this.connection = DriverManager.getConnection("jdbc:sqlite:" + this.path);
|
||||
}
|
||||
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
public void open(String path) throws SQLException
|
||||
{
|
||||
this.path = path;
|
||||
|
||||
this.getConnection();
|
||||
}
|
||||
|
||||
public void close() throws SQLException
|
||||
{
|
||||
if (this.connection == null || this.connection.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getConnection().close();
|
||||
}
|
||||
|
||||
protected void createMigrationsTable() throws SQLException
|
||||
{
|
||||
String sql = """
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
name STRING PRIMARY KEY,
|
||||
timestamp INTEGER
|
||||
);
|
||||
""";
|
||||
|
||||
try (Statement statement = this.getConnection().createStatement()) {
|
||||
statement.execute(sql);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMigration(Class<? extends Migration> migration)
|
||||
{
|
||||
this.migrations.put(migration.getSimpleName(), migration);
|
||||
}
|
||||
|
||||
public boolean hasMigrationRan(String name) throws SQLException
|
||||
{
|
||||
String sql = "SELECT 1 FROM migrations WHERE name = ?;";
|
||||
|
||||
try (PreparedStatement statement = this.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, name);
|
||||
|
||||
ResultSet result = statement.executeQuery();
|
||||
|
||||
return result.next();
|
||||
}
|
||||
}
|
||||
|
||||
public void markMigrationAsRan(String name) throws SQLException
|
||||
{
|
||||
String sql = "INSERT INTO migrations (name, timestamp) VALUES (?, ?);";
|
||||
|
||||
try (PreparedStatement statement = this.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, name);
|
||||
statement.setInt(2, (int) (System.currentTimeMillis() / 1000));
|
||||
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void migrate() throws Exception
|
||||
{
|
||||
this.createMigrationsTable();
|
||||
|
||||
for (String name : this.migrations.keySet()) {
|
||||
if (this.hasMigrationRan(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Migration migration = this.migrations.get(name).getConstructor(this.getClass()).newInstance(this);
|
||||
|
||||
migration.up();
|
||||
|
||||
this.markMigrationAsRan(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.migrations;
|
||||
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
|
||||
import java.sql.Statement;
|
||||
|
||||
public class CreateLodsTable extends Migration
|
||||
{
|
||||
public CreateLodsTable(Database database)
|
||||
{
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void up() throws Exception
|
||||
{
|
||||
try (Statement statement = this.database.getConnection().createStatement()) {
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS lods (
|
||||
worldId STRING NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
beacons BLOB NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
PRIMARY KEY (worldId, x, z)
|
||||
);
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.migrations;
|
||||
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
|
||||
public abstract class Migration
|
||||
{
|
||||
protected Database database;
|
||||
|
||||
public Migration(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public abstract void up() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.models;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LodModel extends Model
|
||||
{
|
||||
public static LodModel create()
|
||||
{
|
||||
return new LodModel();
|
||||
}
|
||||
|
||||
protected UUID worldId;
|
||||
|
||||
protected int x;
|
||||
|
||||
protected int z;
|
||||
|
||||
protected byte[] data;
|
||||
|
||||
protected byte[] beacons;
|
||||
|
||||
protected int timestamp;
|
||||
|
||||
public LodModel setWorldId(UUID worldId)
|
||||
{
|
||||
this.worldId = worldId;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public UUID getWorldId()
|
||||
{
|
||||
return this.worldId;
|
||||
}
|
||||
|
||||
public LodModel setX(int x)
|
||||
{
|
||||
this.x = x;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
public LodModel setZ(int z)
|
||||
{
|
||||
this.z = z;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getZ()
|
||||
{
|
||||
return z;
|
||||
}
|
||||
|
||||
public LodModel setData(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public LodModel setBeacons(byte[] beacons)
|
||||
{
|
||||
this.beacons = beacons;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getBeacons()
|
||||
{
|
||||
return beacons;
|
||||
}
|
||||
|
||||
public LodModel setTimestamp(int timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTimestamp()
|
||||
{
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return this.getWorldId() + "@" + this.getX() + "x" + this.getZ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.models;
|
||||
|
||||
public abstract class Model
|
||||
{
|
||||
public abstract String toString();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.repositories;
|
||||
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
import no.jckf.dhsupport.core.database.models.LodModel;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AsyncLodRepository extends LodRepository
|
||||
{
|
||||
protected Executor executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public AsyncLodRepository(Database database)
|
||||
{
|
||||
super(database);
|
||||
}
|
||||
|
||||
protected <T> CompletableFuture<T> queueTask(Supplier<T> task)
|
||||
{
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
future.complete(task.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<LodModel> saveLodAsync(UUID worldId, int sectionX, int sectionZ, byte[] data, byte[] beacons)
|
||||
{
|
||||
return this.queueTask(() -> this.saveLod(worldId, sectionX, sectionZ, data, beacons));
|
||||
}
|
||||
|
||||
public CompletableFuture<LodModel> loadLodAsync(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
return this.queueTask(() -> this.loadLod(worldId, sectionX, sectionZ));
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> lodExistsAsync(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
return this.queueTask(() -> this.lodExists(worldId, sectionX, sectionZ));
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> deleteLodAsync(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
return this.queueTask(() -> this.deleteLod(worldId, sectionX, sectionZ));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.database.repositories;
|
||||
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
import no.jckf.dhsupport.core.database.models.LodModel;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class LodRepository
|
||||
{
|
||||
protected Database database;
|
||||
|
||||
protected Logger logger;
|
||||
|
||||
public LodRepository(Database database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void setLogger(Logger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Logger getLogger()
|
||||
{
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
public LodModel saveLod(UUID worldId, int sectionX, int sectionZ, byte[] data, byte[] beacons)
|
||||
{
|
||||
int timestamp = (int) (System.currentTimeMillis() / 1000);
|
||||
|
||||
String sql = "REPLACE INTO lods (worldId, x, z, data, beacons, timestamp) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try (PreparedStatement statement = this.database.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
statement.setBytes(4, data);
|
||||
statement.setBytes(5, beacons);
|
||||
statement.setInt(6, timestamp);
|
||||
|
||||
statement.executeUpdate();
|
||||
|
||||
return LodModel.create()
|
||||
.setWorldId(worldId)
|
||||
.setX(sectionX)
|
||||
.setZ(sectionZ)
|
||||
.setData(data)
|
||||
.setBeacons(beacons)
|
||||
.setTimestamp(timestamp);
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not save LOD: " + exception);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public LodModel loadLod(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
String sql = "SELECT data, beacons, timestamp FROM lods WHERE worldId = ? AND x = ? AND z = ? LIMIT 1";
|
||||
|
||||
try (PreparedStatement statement = this.database.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
ResultSet result = statement.executeQuery();
|
||||
|
||||
byte[] data = result.getBytes("data");
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LodModel.create()
|
||||
.setWorldId(worldId)
|
||||
.setX(sectionX)
|
||||
.setZ(sectionZ)
|
||||
.setData(data)
|
||||
.setBeacons(result.getBytes("beacons"))
|
||||
.setTimestamp(result.getInt("timestamp"));
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not load LOD: " + exception);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean lodExists(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
String sql = "SELECT EXISTS( SELECT 1 FROM lods WHERE worldId = ? AND x = ? AND z = ? )";
|
||||
|
||||
try (PreparedStatement statement = this.database.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
ResultSet result = statement.executeQuery();
|
||||
|
||||
return result.getInt(1) == 1;
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not check LOD existence: " + exception);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteLod(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
String sql = "DELETE FROM lods WHERE worldId = ? AND x = ? AND z = ?";
|
||||
|
||||
try (PreparedStatement statement = this.database.getConnection().prepareStatement(sql)) {
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
statement.executeUpdate();
|
||||
|
||||
return true;
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not delete LOD: " + exception);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.dataobject;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class Beacon extends DataObject
|
||||
{
|
||||
protected BlockPosition position;
|
||||
|
||||
protected int color;
|
||||
|
||||
public Beacon()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Beacon(BlockPosition position, int color)
|
||||
{
|
||||
this.position = position;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Beacon(int x, int y, int z, int color)
|
||||
{
|
||||
this(new BlockPosition(x, y, z), color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
this.position.encode(encoder);
|
||||
|
||||
encoder.writeInt(this.color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.dataobject;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class BlockPosition extends DataObject
|
||||
{
|
||||
protected int x;
|
||||
|
||||
protected int y;
|
||||
|
||||
protected int z;
|
||||
|
||||
public BlockPosition()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BlockPosition(int x, int y, int z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public void setX(int x)
|
||||
{
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public int getX()
|
||||
{
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public void setY(int y)
|
||||
{
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getY()
|
||||
{
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public void setZ(int z)
|
||||
{
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int getZ()
|
||||
{
|
||||
return this.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.x);
|
||||
encoder.writeInt(this.y);
|
||||
encoder.writeInt(this.z);
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,16 @@
|
||||
|
||||
package no.jckf.dhsupport.core.dataobject;
|
||||
|
||||
import net.jpountz.lz4.LZ4Factory;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import net.jpountz.xxhash.XXHashFactory;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.enums.CompressionType;
|
||||
import no.jckf.dhsupport.core.enums.GenerationStep;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.tukaani.xz.LZMA2Options;
|
||||
import org.tukaani.xz.XZ;
|
||||
import org.tukaani.xz.XZOutputStream;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Lod extends DataObject
|
||||
{
|
||||
public static int dataFormatVersion = 1;
|
||||
|
||||
public static CompressionType compressionType = CompressionType.LZ4;
|
||||
public static CompressionType compressionType = CompressionType.LZMA2;
|
||||
|
||||
public static int width = 64;
|
||||
|
||||
@@ -140,14 +140,7 @@ public class Lod extends DataObject
|
||||
ByteArrayOutputStream compressedStream = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
LZ4FrameOutputStream compressorStream = new LZ4FrameOutputStream(
|
||||
new BufferedOutputStream(compressedStream),
|
||||
LZ4FrameOutputStream.BLOCKSIZE.SIZE_4MB,
|
||||
uncompressedData.length,
|
||||
LZ4Factory.fastestInstance().highCompressor(17),
|
||||
XXHashFactory.fastestInstance().hash32(),
|
||||
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE
|
||||
);
|
||||
OutputStream compressorStream = new XZOutputStream(compressedStream, new LZMA2Options(3), XZ.CHECK_CRC64);
|
||||
|
||||
compressorStream.write(uncompressedData);
|
||||
compressorStream.flush();
|
||||
|
||||
@@ -41,6 +41,18 @@ public class SectionPosition extends DataObject
|
||||
|
||||
protected int z;
|
||||
|
||||
public SectionPosition()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SectionPosition(int x, int z, int detailLevel)
|
||||
{
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
this.detailLevel = detailLevel;
|
||||
}
|
||||
|
||||
public void setDetailLevel(int level)
|
||||
{
|
||||
this.detailLevel = level;
|
||||
|
||||
@@ -18,10 +18,17 @@
|
||||
|
||||
package no.jckf.dhsupport.core.handler;
|
||||
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.message.plugin.ExceptionMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.FullDataChunkMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.FullDataSourceRequestMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.FullDataSourceResponseMessage;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -48,38 +55,120 @@ public class LodHandler
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(FullDataSourceRequestMessage.class, (requestMessage) -> {
|
||||
//this.dhSupport.info("LOD request for " + requestMessage.getPosition().getX() + " x " + requestMessage.getPosition().getZ());
|
||||
|
||||
int myBufferId = this.bufferId++;
|
||||
if (requestMessage.getPosition().getDetailLevel() != 6) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_SECTION_REQUIRES_SPLITTING);
|
||||
exceptionMessage.setMessage("Only detail level 6 is supported");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
UUID worldUuid = Bukkit.getPlayer(requestMessage.getSender()).getWorld().getUID();
|
||||
|
||||
this.dhSupport.getLodData(worldUuid, requestMessage.getPosition())
|
||||
.thenAccept((lod) -> {
|
||||
int chunkCount = (int) Math.ceil((double) lod.length / CHUNK_SIZE);
|
||||
WorldInterface world = this.dhSupport.getWorldInterface(worldUuid);
|
||||
|
||||
for (int chunkNo = 0; chunkNo < chunkCount; chunkNo++) {
|
||||
FullDataChunkMessage chunkResponse = new FullDataChunkMessage();
|
||||
chunkResponse.setBufferId(myBufferId);
|
||||
chunkResponse.setIsFirst(chunkNo == 0);
|
||||
chunkResponse.setData(Arrays.copyOfRange(
|
||||
lod,
|
||||
CHUNK_SIZE * chunkNo,
|
||||
Math.min(CHUNK_SIZE * chunkNo + CHUNK_SIZE, lod.length)
|
||||
));
|
||||
Configuration config = world.getConfig();
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), chunkResponse);
|
||||
SectionPosition position = requestMessage.getPosition();
|
||||
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
String borderCenter = config.getString(DhsConfig.BORDER_CENTER);
|
||||
Integer borderRadius = config.getInt(DhsConfig.BORDER_RADIUS);
|
||||
|
||||
if (borderCenter != null && borderRadius != null) {
|
||||
String[] centerXz = borderCenter.split(",");
|
||||
|
||||
if (centerXz.length != 2) {
|
||||
this.dhSupport.warning("Border for world " + world.getName() + " is misconfigured.");
|
||||
} else {
|
||||
int centerX = Integer.parseInt(centerXz[0]);
|
||||
int centerZ = Integer.parseInt(centerXz[1]);
|
||||
|
||||
int minX = centerX - borderRadius;
|
||||
int maxX = centerX + borderRadius;
|
||||
|
||||
int minZ = centerZ - borderRadius;
|
||||
int maxZ = centerZ + borderRadius;
|
||||
|
||||
int higherLodX = worldX + 64;
|
||||
int higherLodZ = worldZ + 64;
|
||||
|
||||
if (higherLodX < minX || worldX > maxX || higherLodZ < minZ || worldZ > maxZ) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("World border");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*boolean generate = config.getBool(DhsConfig.GENERATE_NEW_CHUNKS);
|
||||
|
||||
if (!generate) {
|
||||
for (int relativeChunkX = 0; relativeChunkX < Lod.width / 16; relativeChunkX++) {
|
||||
for (int relativeChunkZ = 0; relativeChunkZ < Lod.width / 16; relativeChunkZ++) {
|
||||
if (!world.chunkExists(worldX + relativeChunkX * 16, worldZ + relativeChunkZ * 16)) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("Fog of war");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
this.dhSupport.getLod(worldUuid, position)
|
||||
.thenAccept((lodModel) -> {
|
||||
FullDataSourceResponseMessage responseMessage = new FullDataSourceResponseMessage();
|
||||
responseMessage.isResponseTo(requestMessage);
|
||||
responseMessage.setBufferId(myBufferId);
|
||||
|
||||
boolean sendData = requestMessage.getTimestamp() == null || (requestMessage.getTimestamp() / 1000) < lodModel.getTimestamp();
|
||||
|
||||
if (sendData) {
|
||||
int myBufferId = this.bufferId++;
|
||||
|
||||
responseMessage.setBufferId(myBufferId);
|
||||
responseMessage.setBeacons(lodModel.getBeacons());
|
||||
|
||||
byte[] data = lodModel.getData();
|
||||
|
||||
int chunkCount = (int) Math.ceil((double) data.length / CHUNK_SIZE);
|
||||
|
||||
for (int chunkNo = 0; chunkNo < chunkCount; chunkNo++) {
|
||||
FullDataChunkMessage chunkResponse = new FullDataChunkMessage();
|
||||
chunkResponse.setBufferId(myBufferId);
|
||||
chunkResponse.setIsFirst(chunkNo == 0);
|
||||
chunkResponse.setData(Arrays.copyOfRange(
|
||||
data,
|
||||
CHUNK_SIZE * chunkNo,
|
||||
Math.min(CHUNK_SIZE * chunkNo + CHUNK_SIZE, data.length)
|
||||
));
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), chunkResponse);
|
||||
}
|
||||
|
||||
//this.dhSupport.info("LOD in " + chunkCount + " parts sent for " + requestMessage.getPosition().getX() + " x " + requestMessage.getPosition().getZ());
|
||||
}
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), responseMessage);
|
||||
|
||||
//this.dhSupport.info("LOD in " + chunkCount + " parts sent for " + requestMessage.getPosition().getX() + " x " + requestMessage.getPosition().getZ());
|
||||
})
|
||||
.exceptionally((exception) -> {
|
||||
this.dhSupport.warning(exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("Internal error");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,9 +21,10 @@ package no.jckf.dhsupport.core.handler;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.message.plugin.CurrentLevelKeyMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.LevelInitMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.RemotePlayerConfigMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PlayerConfigHandler
|
||||
{
|
||||
@@ -41,18 +42,28 @@ public class PlayerConfigHandler
|
||||
{
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(RemotePlayerConfigMessage.class, (configMessage) -> {
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
// TODO: Should this be unique?
|
||||
String levelKey = Bukkit.getPlayer(configMessage.getSender()).getWorld().getName();
|
||||
Player player = Bukkit.getPlayer(configMessage.getSender());
|
||||
|
||||
CurrentLevelKeyMessage levelKeyResponse = new CurrentLevelKeyMessage();
|
||||
Configuration dhsConfig = this.dhSupport.getWorldInterface(player.getWorld().getUID()).getConfig();
|
||||
|
||||
String levelKeyPrefix = dhsConfig.getString(DhsConfig.LEVEL_KEY_PREFIX);
|
||||
String levelKey = player.getWorld().getName();
|
||||
|
||||
if (levelKeyPrefix != null) {
|
||||
levelKey = levelKeyPrefix + levelKey;
|
||||
}
|
||||
|
||||
this.dhSupport.info("Received DH config for " + player.getName() + " in " + levelKey);
|
||||
|
||||
LevelInitMessage levelKeyResponse = new LevelInitMessage();
|
||||
levelKeyResponse.setKey(levelKey);
|
||||
levelKeyResponse.setTime(System.currentTimeMillis());
|
||||
this.pluginMessageHandler.sendPluginMessage(configMessage.getSender(), levelKeyResponse);
|
||||
|
||||
Configuration dhsConfig = this.dhSupport.getConfig();
|
||||
Configuration clientConfig = configMessage.toConfiguration();
|
||||
|
||||
// This is not very flexible, but will do for now.
|
||||
for (String key : DhsConfig.getKeys()) {
|
||||
for (String key : RemotePlayerConfigMessage.KEYS) {
|
||||
//this.dhSupport.getLogger().info("Config key " + key + ":");
|
||||
|
||||
Object dhsValue = dhsConfig.get(key);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PluginMessageHandler
|
||||
|
||||
public final String pluginChannel = "distant_horizons:message";
|
||||
|
||||
public final int protocolVersion = 3;
|
||||
public final int protocolVersion = 8;
|
||||
|
||||
private EventBus<PluginMessage> eventBus;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class PluginMessageHandler
|
||||
this.messageTypeRegistry = new MessageTypeRegistry();
|
||||
this.messageTypeRegistry.registerMessageType(0, null);
|
||||
this.messageTypeRegistry.registerMessageType(1, CloseReasonMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(2, CurrentLevelKeyMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(2, LevelInitMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(3, RemotePlayerConfigMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(4, CancelMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(5, ExceptionMessage.class);
|
||||
@@ -105,6 +105,10 @@ public class PluginMessageHandler
|
||||
|
||||
Decoder decoder = new Decoder(data);
|
||||
|
||||
#if READ_FORGE_BYTE == "true"
|
||||
decoder.readByte();
|
||||
#endif
|
||||
|
||||
// Read the client's protocol version.
|
||||
short protocolVersion = decoder.readShort();
|
||||
|
||||
@@ -154,12 +158,17 @@ public class PluginMessageHandler
|
||||
message.encode(encoder);
|
||||
data = encoder.toByteArray();
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass() + ": " + exception.getClass() + " - " + exception.getMessage());
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass().getSimpleName());
|
||||
exception.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
#if READ_FORGE_BYTE == "true"
|
||||
encoder.writeByte(0);
|
||||
#endif
|
||||
|
||||
encoder.writeShort(this.protocolVersion);
|
||||
encoder.writeShort(messageTypeId);
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.lodbuilders;
|
||||
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.dataobject.DataPoint;
|
||||
import no.jckf.dhsupport.core.dataobject.IdMapping;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FastOverworldBuilder extends LodBuilder
|
||||
{
|
||||
public FastOverworldBuilder(WorldInterface worldInterface, SectionPosition position)
|
||||
{
|
||||
super(worldInterface, position);
|
||||
}
|
||||
|
||||
public Lod generate()
|
||||
{
|
||||
int minY = this.worldInterface.getMinY();
|
||||
int maxY = this.worldInterface.getMaxY();
|
||||
int height = maxY - minY;
|
||||
|
||||
int seaLevel = this.worldInterface.getSeaLevel();
|
||||
int relativeSeaLevel = seaLevel - minY;
|
||||
|
||||
int offsetX = Coordinates.sectionToBlock(this.position.getX());
|
||||
int offsetZ = Coordinates.sectionToBlock(this.position.getZ());
|
||||
|
||||
int yStep = this.worldInterface.getConfig().getInt(DhsConfig.BUILDER_RESOLUTION);
|
||||
|
||||
boolean scanToSeaLevel = this.worldInterface.getConfig().getBool(DhsConfig.SCAN_TO_SEA_LEVEL, false);
|
||||
boolean includeNonCollidingTopLayer = this.worldInterface.getConfig().getBool(DhsConfig.INCLUDE_NON_COLLIDING_TOP_LAYER, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>();
|
||||
|
||||
for (int relativeX = 0; relativeX < Lod.width; relativeX++) {
|
||||
for (int relativeZ = 0; relativeZ < Lod.width; relativeZ++) {
|
||||
int worldX = offsetX + relativeX;
|
||||
int worldZ = offsetZ + relativeZ;
|
||||
|
||||
// Actual Y of top-most block.
|
||||
int topLayer = this.worldInterface.getHighestYAt(worldX, worldZ);
|
||||
|
||||
// Copies of the original values.
|
||||
int hardTopLayer = topLayer;
|
||||
int originalStep = yStep;
|
||||
|
||||
if (includeNonCollidingTopLayer) {
|
||||
outer: while (topLayer + 1 < maxY) {
|
||||
String topSample = this.worldInterface.getMaterialAt(worldX, topLayer + 1, worldZ);
|
||||
|
||||
switch (topSample) {
|
||||
case "minecraft:air":
|
||||
case "minecraft:void_air":
|
||||
break outer;
|
||||
}
|
||||
|
||||
topLayer++;
|
||||
}
|
||||
}
|
||||
|
||||
// If these differ, the top layer is non-colliding and likely requires yStep=1.
|
||||
if (topLayer != hardTopLayer) {
|
||||
yStep = 1;
|
||||
}
|
||||
|
||||
// Distance from bottom to top-most block.
|
||||
int relativeTopLayer = topLayer - minY;
|
||||
|
||||
String biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
|
||||
List<DataPoint> column = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
DataPoint previous = null;
|
||||
|
||||
@Nullable
|
||||
Integer solidGround = null;
|
||||
|
||||
int firstY = height - yStep;
|
||||
|
||||
for (int relativeY = firstY; (solidGround == null || relativeY >= solidGround) && relativeY >= 1 - yStep; relativeY -= yStep) {
|
||||
int thisStep = yStep;
|
||||
|
||||
if (relativeY < 0) {
|
||||
thisStep -= -relativeY;
|
||||
relativeY = 0;
|
||||
}
|
||||
|
||||
int lowWorldY = minY + relativeY;
|
||||
int highWorldY = lowWorldY + thisStep - 1;
|
||||
|
||||
// We've reached the top-most colliding block. Restore yStep.
|
||||
if (highWorldY == hardTopLayer) {
|
||||
yStep = originalStep;
|
||||
}
|
||||
|
||||
String material = this.worldInterface.getMaterialAt(worldX, highWorldY, worldZ);
|
||||
|
||||
if (solidGround == null && (!scanToSeaLevel || highWorldY <= seaLevel)) {
|
||||
switch (material) {
|
||||
case "minecraft:stone":
|
||||
case "minecraft:grass":
|
||||
case "minecraft:dirt":
|
||||
case "minecraft:gravel":
|
||||
case "minecraft:sand":
|
||||
case "minecraft:sandstone":
|
||||
case "minecraft:mycelium":
|
||||
solidGround = Math.min(relativeY - 10, relativeSeaLevel - 10);
|
||||
}
|
||||
}
|
||||
|
||||
String compositeKey = biome + "|" + material;
|
||||
|
||||
@Nullable
|
||||
Integer id = mapMap.get(compositeKey);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, null));
|
||||
id = idMappings.size() - 1;
|
||||
mapMap.put(compositeKey, id);
|
||||
}
|
||||
|
||||
DataPoint point;
|
||||
|
||||
if (previous != null && previous.getMappingId() == id) {
|
||||
point = previous;
|
||||
|
||||
point.setStartY(point.getStartY() - thisStep);
|
||||
point.setHeight(point.getHeight() + thisStep);
|
||||
} else {
|
||||
point = new DataPoint();
|
||||
column.add(point);
|
||||
|
||||
point.setStartY(relativeY);
|
||||
point.setHeight(thisStep);
|
||||
point.setMappingId(id);
|
||||
|
||||
if (highWorldY + 1 < maxY) {
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
}
|
||||
|
||||
if (material.equals("minecraft:air") || material.equals("minecraft:void_air")) {
|
||||
// Start by filling the top of the column with air, then jump down to the top layer.
|
||||
if (relativeY == firstY) {
|
||||
point.setStartY(relativeTopLayer + 1);
|
||||
point.setHeight(height - relativeTopLayer);
|
||||
|
||||
relativeY = point.getStartY();
|
||||
} else {
|
||||
// Encountered air that is below a non-air block. Set yStep=1 to avoid stretching the air into the ground or sea.
|
||||
yStep = 1;
|
||||
}
|
||||
} else {
|
||||
// Entered a new material. Reset yStep in case we came from air with yStep=1.
|
||||
yStep = originalStep;
|
||||
}
|
||||
}
|
||||
|
||||
previous = point;
|
||||
}
|
||||
|
||||
columns.add(column);
|
||||
}
|
||||
}
|
||||
|
||||
return new Lod(this.worldInterface, this.position, idMappings, columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.lodbuilders;
|
||||
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.dataobject.DataPoint;
|
||||
import no.jckf.dhsupport.core.dataobject.IdMapping;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FullBuilder extends LodBuilder
|
||||
{
|
||||
public FullBuilder(WorldInterface worldInterface, SectionPosition position)
|
||||
{
|
||||
super(worldInterface, position);
|
||||
}
|
||||
|
||||
public Lod generate()
|
||||
{
|
||||
int minY = this.worldInterface.getMinY();
|
||||
int maxY = this.worldInterface.getMaxY();
|
||||
int height = maxY - minY;
|
||||
|
||||
int offsetX = Coordinates.sectionToBlock(this.position.getX());
|
||||
int offsetZ = Coordinates.sectionToBlock(this.position.getZ());
|
||||
|
||||
int yStep = this.worldInterface.getConfig().getInt(DhsConfig.BUILDER_RESOLUTION);
|
||||
|
||||
boolean includeNonCollidingTopLayer = this.worldInterface.getConfig().getBool(DhsConfig.INCLUDE_NON_COLLIDING_TOP_LAYER, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>();
|
||||
|
||||
for (int relativeX = 0; relativeX < Lod.width; relativeX++) {
|
||||
for (int relativeZ = 0; relativeZ < Lod.width; relativeZ++) {
|
||||
int worldX = offsetX + relativeX;
|
||||
int worldZ = offsetZ + relativeZ;
|
||||
|
||||
// Actual Y of top-most block.
|
||||
int topLayer = this.worldInterface.getHighestYAt(worldX, worldZ);
|
||||
|
||||
// Copies of the original values.
|
||||
int hardTopLayer = topLayer;
|
||||
int originalStep = yStep;
|
||||
|
||||
if (includeNonCollidingTopLayer) {
|
||||
outer: while (topLayer + 1 < maxY) {
|
||||
String topSample = this.worldInterface.getMaterialAt(worldX, topLayer + 1, worldZ);
|
||||
|
||||
switch (topSample) {
|
||||
case "minecraft:air":
|
||||
case "minecraft:void_air":
|
||||
break outer;
|
||||
}
|
||||
|
||||
topLayer++;
|
||||
}
|
||||
}
|
||||
|
||||
// If these differ, the top layer is non-colliding and likely requires yStep=1.
|
||||
if (topLayer != hardTopLayer) {
|
||||
yStep = 1;
|
||||
}
|
||||
|
||||
// Distance from bottom to top-most block.
|
||||
int relativeTopLayer = topLayer - minY;
|
||||
|
||||
String biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
|
||||
List<DataPoint> column = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
DataPoint previous = null;
|
||||
|
||||
int firstY = height - yStep;
|
||||
|
||||
for (int relativeY = firstY; relativeY >= 1 - yStep; relativeY -= yStep) {
|
||||
int thisStep = yStep;
|
||||
|
||||
if (relativeY < 0) {
|
||||
thisStep -= -relativeY;
|
||||
relativeY = 0;
|
||||
}
|
||||
|
||||
int lowWorldY = minY + relativeY;
|
||||
int highWorldY = lowWorldY + thisStep - 1;
|
||||
|
||||
// We've reached the top-most colliding block. Restore yStep.
|
||||
if (highWorldY == hardTopLayer) {
|
||||
yStep = originalStep;
|
||||
}
|
||||
|
||||
String material = this.worldInterface.getMaterialAt(worldX, highWorldY, worldZ);
|
||||
|
||||
String compositeKey = biome + "|" + material + "|" + this.worldInterface.getBlockStateAsStringAt(worldX, highWorldY, worldZ);
|
||||
|
||||
@Nullable
|
||||
Integer id = mapMap.get(compositeKey);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, this.worldInterface.getBlockPropertiesAt(worldX, highWorldY, worldZ)));
|
||||
id = idMappings.size() - 1;
|
||||
mapMap.put(compositeKey, id);
|
||||
}
|
||||
|
||||
DataPoint point;
|
||||
|
||||
if (previous != null && previous.getMappingId() == id) {
|
||||
point = previous;
|
||||
|
||||
point.setStartY(point.getStartY() - thisStep);
|
||||
point.setHeight(point.getHeight() + thisStep);
|
||||
} else {
|
||||
point = new DataPoint();
|
||||
column.add(point);
|
||||
|
||||
point.setStartY(relativeY);
|
||||
point.setHeight(thisStep);
|
||||
point.setMappingId(id);
|
||||
|
||||
if (highWorldY + 1 < maxY) {
|
||||
point.setSkyLight(this.worldInterface.getSkyLightAt(worldX, highWorldY + 1, worldZ));
|
||||
point.setBlockLight(this.worldInterface.getBlockLightAt(worldX, highWorldY + 1, worldZ));
|
||||
}
|
||||
|
||||
if (material.equals("minecraft:air") || material.equals("minecraft:void_air")) {
|
||||
// Start by filling the top of the column with air, then jump down to the top layer.
|
||||
if (relativeY == firstY) {
|
||||
point.setStartY(relativeTopLayer + 1);
|
||||
point.setHeight(height - relativeTopLayer);
|
||||
|
||||
relativeY = point.getStartY();
|
||||
} else {
|
||||
// Encountered air that is below a non-air block. Set yStep=1 to avoid stretching the air into the ground or sea.
|
||||
yStep = 1;
|
||||
}
|
||||
} else {
|
||||
// Entered a new material. Reset yStep in case we came from air with yStep=1.
|
||||
yStep = originalStep;
|
||||
}
|
||||
}
|
||||
|
||||
previous = point;
|
||||
}
|
||||
|
||||
columns.add(column);
|
||||
}
|
||||
}
|
||||
|
||||
return new Lod(this.worldInterface, this.position, idMappings, columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.lodbuilders;
|
||||
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
public abstract class LodBuilder
|
||||
{
|
||||
protected WorldInterface worldInterface;
|
||||
|
||||
protected SectionPosition position;
|
||||
|
||||
public LodBuilder(WorldInterface worldInterface, SectionPosition position)
|
||||
{
|
||||
this.worldInterface = worldInterface;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public abstract Lod generate();
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class CloseReasonMessage extends PluginMessage
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return reason;
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,9 +24,9 @@ import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
public class ExceptionMessage extends TrackablePluginMessage
|
||||
{
|
||||
public static int TYPE_RATE_LIMITED = 0;
|
||||
public static int TYPE_INVALID_LEVEL = 1;
|
||||
public static int TYPE_INVALID_POSITION = 2;
|
||||
public static int TYPE_REQUEST_REJECTED = 3;
|
||||
public static int TYPE_REQUEST_OUT_OF_RANGE = 1;
|
||||
public static int TYPE_REQUEST_REJECTED = 2;
|
||||
public static int TYPE_SECTION_REQUIRES_SPLITTING = 3;
|
||||
|
||||
protected int typeId;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ExceptionMessage extends TrackablePluginMessage
|
||||
|
||||
public int getTypeId()
|
||||
{
|
||||
return typeId;
|
||||
return this.typeId;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
@@ -49,7 +49,7 @@ public class ExceptionMessage extends TrackablePluginMessage
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
return this.message;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,7 @@ public class FullDataChunkMessage extends PluginMessage
|
||||
|
||||
public int getBufferId()
|
||||
{
|
||||
return bufferId;
|
||||
return this.bufferId;
|
||||
}
|
||||
|
||||
public void setData(byte[] data)
|
||||
@@ -45,7 +45,7 @@ public class FullDataChunkMessage extends PluginMessage
|
||||
|
||||
public byte[] getData()
|
||||
{
|
||||
return data;
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setIsFirst(boolean isFirst)
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ public class FullDataSourceRequestMessage extends TrackablePluginMessage
|
||||
|
||||
public String getWorldName()
|
||||
{
|
||||
return worldName;
|
||||
return this.worldName;
|
||||
}
|
||||
|
||||
public void setPosition(SectionPosition position)
|
||||
@@ -46,7 +46,7 @@ public class FullDataSourceRequestMessage extends TrackablePluginMessage
|
||||
|
||||
public SectionPosition getPosition()
|
||||
{
|
||||
return position;
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp)
|
||||
@@ -56,7 +56,7 @@ public class FullDataSourceRequestMessage extends TrackablePluginMessage
|
||||
|
||||
public Long getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+21
-5
@@ -20,24 +20,40 @@ package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FullDataSourceResponseMessage extends TrackablePluginMessage
|
||||
{
|
||||
protected int bufferId;
|
||||
protected Integer bufferId;
|
||||
|
||||
public void setBufferId(int bufferId)
|
||||
protected byte[] beacons;
|
||||
|
||||
public void setBufferId(@Nullable Integer bufferId)
|
||||
{
|
||||
this.bufferId = bufferId;
|
||||
}
|
||||
|
||||
public int getBufferId()
|
||||
{
|
||||
return bufferId;
|
||||
return this.bufferId;
|
||||
}
|
||||
|
||||
public void setBeacons(byte[] beacons)
|
||||
{
|
||||
this.beacons = beacons;
|
||||
}
|
||||
|
||||
public byte[] getBeacons()
|
||||
{
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeBoolean(true);
|
||||
encoder.writeInt(this.bufferId);
|
||||
if (encoder.writeOptional(this.bufferId)) {
|
||||
encoder.writeInt(this.bufferId);
|
||||
encoder.write(this.beacons);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -21,10 +21,12 @@ package no.jckf.dhsupport.core.message.plugin;
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class CurrentLevelKeyMessage extends PluginMessage
|
||||
public class LevelInitMessage extends PluginMessage
|
||||
{
|
||||
protected String key;
|
||||
|
||||
protected long time;
|
||||
|
||||
public void setKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
@@ -35,15 +37,27 @@ public class CurrentLevelKeyMessage extends PluginMessage
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public void setTime(long time)
|
||||
{
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getTime()
|
||||
{
|
||||
return this.time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeShortString(this.key);
|
||||
encoder.writeLong(this.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.key = decoder.readShortString();
|
||||
this.time = decoder.readLong();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,18 @@ import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
|
||||
public class RemotePlayerConfigMessage extends PluginMessage
|
||||
{
|
||||
public static String[] KEYS = {
|
||||
DhsConfig.DISTANT_GENERATION_ENABLED,
|
||||
DhsConfig.RENDER_DISTANCE,
|
||||
DhsConfig.FULL_DATA_REQUEST_CONCURRENCY_LIMIT,
|
||||
DhsConfig.REAL_TIME_UPDATES_ENABLED,
|
||||
DhsConfig.REAL_TIME_UPDATE_RADIUS,
|
||||
DhsConfig.LOGIN_DATA_SYNC_ENABLED,
|
||||
DhsConfig.LOGIN_DATA_SYNC_RADIUS,
|
||||
DhsConfig.LOGIN_DATA_SYNC_RC_LIMIT,
|
||||
DhsConfig.MAX_DATA_TRANSFER_SPEED,
|
||||
};
|
||||
|
||||
protected int renderDistance;
|
||||
|
||||
protected boolean distantGenerationEnabled;
|
||||
@@ -33,10 +45,16 @@ public class RemotePlayerConfigMessage extends PluginMessage
|
||||
|
||||
protected boolean realTimeUpdatesEnabled;
|
||||
|
||||
protected int realTimeUpdateRadius;
|
||||
|
||||
protected boolean loginDataSyncEnabled;
|
||||
|
||||
protected int loginDataSyncRadius;
|
||||
|
||||
protected int loginDataSyncRcLimit;
|
||||
|
||||
protected int maxDataTransferSpeed;
|
||||
|
||||
public void setRenderDistance(int distance)
|
||||
{
|
||||
this.renderDistance = distance;
|
||||
@@ -64,7 +82,7 @@ public class RemotePlayerConfigMessage extends PluginMessage
|
||||
|
||||
public int getFullDataRequestConcurrencyLimit()
|
||||
{
|
||||
return fullDataRequestConcurrencyLimit;
|
||||
return this.fullDataRequestConcurrencyLimit;
|
||||
}
|
||||
|
||||
public void setRealTimeUpdatesEnabled(boolean enabled)
|
||||
@@ -74,7 +92,17 @@ public class RemotePlayerConfigMessage extends PluginMessage
|
||||
|
||||
public boolean isRealTimeUpdatesEnabled()
|
||||
{
|
||||
return realTimeUpdatesEnabled;
|
||||
return this.realTimeUpdatesEnabled;
|
||||
}
|
||||
|
||||
public void setRealTimeUpdateRadius(int radius)
|
||||
{
|
||||
this.realTimeUpdateRadius = radius;
|
||||
}
|
||||
|
||||
public int getRealTimeUpdateRadius()
|
||||
{
|
||||
return this.realTimeUpdateRadius;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncEnabled(boolean enabled)
|
||||
@@ -84,7 +112,17 @@ public class RemotePlayerConfigMessage extends PluginMessage
|
||||
|
||||
public boolean getLoginDataSyncEnabled()
|
||||
{
|
||||
return loginDataSyncEnabled;
|
||||
return this.loginDataSyncEnabled;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncRadius(int radius)
|
||||
{
|
||||
this.loginDataSyncRadius = radius;
|
||||
}
|
||||
|
||||
public int getLoginDataSyncRadius()
|
||||
{
|
||||
return this.loginDataSyncRadius;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncRcLimit(int limit)
|
||||
@@ -94,51 +132,73 @@ public class RemotePlayerConfigMessage extends PluginMessage
|
||||
|
||||
public int getLoginDataSyncRcLimit()
|
||||
{
|
||||
return loginDataSyncRcLimit;
|
||||
return this.loginDataSyncRcLimit;
|
||||
}
|
||||
|
||||
public void setMaxDataTransferSpeed(int speed)
|
||||
{
|
||||
this.maxDataTransferSpeed = speed;
|
||||
}
|
||||
|
||||
public int getMaxDataTransferSpeed()
|
||||
{
|
||||
return this.maxDataTransferSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.renderDistance);
|
||||
encoder.writeBoolean(this.distantGenerationEnabled);
|
||||
encoder.writeInt(this.renderDistance);
|
||||
encoder.writeInt(this.fullDataRequestConcurrencyLimit);
|
||||
encoder.writeBoolean(this.realTimeUpdatesEnabled);
|
||||
encoder.writeInt(this.realTimeUpdateRadius);
|
||||
encoder.writeBoolean(this.loginDataSyncEnabled);
|
||||
encoder.writeInt(this.loginDataSyncRadius);
|
||||
encoder.writeInt(this.loginDataSyncRcLimit);
|
||||
encoder.writeInt(this.maxDataTransferSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.renderDistance = decoder.readInt();
|
||||
this.distantGenerationEnabled = decoder.readBoolean();
|
||||
this.renderDistance = decoder.readInt();
|
||||
this.fullDataRequestConcurrencyLimit = decoder.readInt();
|
||||
this.realTimeUpdatesEnabled = decoder.readBoolean();
|
||||
this.realTimeUpdateRadius = decoder.readInt();
|
||||
this.loginDataSyncEnabled = decoder.readBoolean();
|
||||
this.loginDataSyncRadius = decoder.readInt();
|
||||
this.loginDataSyncRcLimit = decoder.readInt();
|
||||
this.maxDataTransferSpeed = decoder.readInt();
|
||||
}
|
||||
|
||||
public void fromConfiguration(Configuration config)
|
||||
{
|
||||
this.renderDistance = config.getInt(DhsConfig.RENDER_DISTANCE);
|
||||
this.distantGenerationEnabled = config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED);
|
||||
this.renderDistance = config.getInt(DhsConfig.RENDER_DISTANCE);
|
||||
this.fullDataRequestConcurrencyLimit = config.getInt(DhsConfig.FULL_DATA_REQUEST_CONCURRENCY_LIMIT);
|
||||
this.realTimeUpdatesEnabled = config.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED);
|
||||
this.realTimeUpdateRadius = config.getInt(DhsConfig.REAL_TIME_UPDATE_RADIUS);
|
||||
this.loginDataSyncEnabled = config.getBool(DhsConfig.LOGIN_DATA_SYNC_ENABLED);
|
||||
this.loginDataSyncRadius = config.getInt(DhsConfig.LOGIN_DATA_SYNC_RADIUS);
|
||||
this.loginDataSyncRcLimit = config.getInt(DhsConfig.LOGIN_DATA_SYNC_RC_LIMIT);
|
||||
this.maxDataTransferSpeed = config.getInt(DhsConfig.MAX_DATA_TRANSFER_SPEED);
|
||||
}
|
||||
|
||||
public Configuration toConfiguration()
|
||||
{
|
||||
Configuration config = new Configuration();
|
||||
|
||||
config.set(DhsConfig.RENDER_DISTANCE, this.renderDistance);
|
||||
config.set(DhsConfig.DISTANT_GENERATION_ENABLED, this.distantGenerationEnabled);
|
||||
config.set(DhsConfig.RENDER_DISTANCE, this.renderDistance);
|
||||
config.set(DhsConfig.FULL_DATA_REQUEST_CONCURRENCY_LIMIT, this.fullDataRequestConcurrencyLimit);
|
||||
config.set(DhsConfig.REAL_TIME_UPDATES_ENABLED, this.realTimeUpdatesEnabled);
|
||||
config.set(DhsConfig.REAL_TIME_UPDATE_RADIUS, this.realTimeUpdateRadius);
|
||||
config.set(DhsConfig.LOGIN_DATA_SYNC_ENABLED, this.loginDataSyncEnabled);
|
||||
config.set(DhsConfig.LOGIN_DATA_SYNC_RADIUS, this.loginDataSyncRadius);
|
||||
config.set(DhsConfig.LOGIN_DATA_SYNC_RC_LIMIT, this.loginDataSyncRcLimit);
|
||||
config.set(DhsConfig.MAX_DATA_TRANSFER_SPEED, this.maxDataTransferSpeed);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class TrackablePluginMessage extends PluginMessage
|
||||
|
||||
public int getTracker()
|
||||
{
|
||||
return tracker;
|
||||
return this.tracker;
|
||||
}
|
||||
|
||||
public void isResponseTo(TrackablePluginMessage other)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* DH Support, server-side support for Distant Horizons.
|
||||
* Copyright (C) 2024 Jim C K Flaten
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.scheduling;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface Scheduler
|
||||
{
|
||||
boolean canReadWorldAsync();
|
||||
|
||||
<U> CompletableFuture<U> runOnMainThread(Supplier<U> supplier);
|
||||
|
||||
<U> CompletableFuture<U> runOnRegionThread(UUID worldId, int x, int z, Supplier<U> supplier);
|
||||
|
||||
<U> CompletableFuture<U> runOnSeparateThread(Supplier<U> supplier);
|
||||
}
|
||||
@@ -18,12 +18,32 @@
|
||||
|
||||
package no.jckf.dhsupport.core.world;
|
||||
|
||||
import java.util.Map;
|
||||
import no.jckf.dhsupport.core.configuration.Configurable;
|
||||
import no.jckf.dhsupport.core.dataobject.Beacon;
|
||||
|
||||
public interface WorldInterface
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface WorldInterface extends Configurable
|
||||
{
|
||||
WorldInterface newInstance();
|
||||
|
||||
UUID getId();
|
||||
|
||||
String getName();
|
||||
|
||||
boolean chunkExists(int x, int z);
|
||||
|
||||
boolean loadChunk(int x, int z);
|
||||
|
||||
CompletableFuture<Boolean> loadChunkAsync(int x, int z);
|
||||
|
||||
boolean unloadChunk(int x, int z);
|
||||
|
||||
boolean unloadChunkAsync(int x, int z);
|
||||
|
||||
int getMinY();
|
||||
|
||||
int getMaxY();
|
||||
@@ -44,5 +64,5 @@ public interface WorldInterface
|
||||
|
||||
byte getSkyLightAt(int x, int y, int z);
|
||||
|
||||
boolean isTransparent(int x, int y, int z);
|
||||
Collection<Beacon> getBeaconsInChunk(int x, int z);
|
||||
}
|
||||
@@ -1,18 +1,98 @@
|
||||
# Maximum supported render distance. Clients will be capped to this value.
|
||||
# Required.
|
||||
render_distance: 128
|
||||
# Default: 1024
|
||||
render_distance: 1024
|
||||
|
||||
# TODO
|
||||
# Is LOD generation enabled at all?
|
||||
# Disable if you only want to use the plugin for level key info in a proxied setup.
|
||||
# Required.
|
||||
# Default: true
|
||||
distant_generation_enabled: true
|
||||
|
||||
# TODO
|
||||
# Number of parallel LOD requests per client.
|
||||
# Required.
|
||||
# Default: 20
|
||||
full_data_request_concurrency_limit: 20
|
||||
|
||||
# TODO
|
||||
real_time_updates_enabled: true
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: false
|
||||
real_time_updates_enabled: false
|
||||
|
||||
# TODO
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: 128
|
||||
real_time_update_radius: 128
|
||||
|
||||
# Let clients ask for updates to LODs they've previously downloaded.
|
||||
# Required.
|
||||
# Default: true
|
||||
login_data_sync_enabled: true
|
||||
|
||||
# TODO
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: 128
|
||||
login_data_sync_radius: 128
|
||||
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: 50
|
||||
login_data_sync_rc_limit: 50
|
||||
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: 0
|
||||
max_data_transfer_speed: 0
|
||||
|
||||
# Select which LOD builder to use.
|
||||
# Required.
|
||||
# Default: FullBuilder
|
||||
builder_type: FullBuilder
|
||||
|
||||
# Number of blocks to advance between each sample on the Y axis when generating LODs.
|
||||
# A setting of 1 will sample every block, while 2 will sample every other block, and so on.
|
||||
# Required.
|
||||
# Default: 2
|
||||
builder_resolution: 2
|
||||
|
||||
# Whether to include non-colliding blocks when they are the top-most block.
|
||||
# True means tall grass, flowers, and snow will color the block below them.
|
||||
# Optional.
|
||||
# Default: false
|
||||
#include_non-colliding_top_layer: true
|
||||
|
||||
# When running DHS on a proxied server, set this to a value that is unique to this specific server.
|
||||
# Optional.
|
||||
#level_key_prefix:
|
||||
|
||||
# World specific settings.
|
||||
# You can override any of the above settings on a per-world basis.
|
||||
worlds:
|
||||
world:
|
||||
# Use builder that is optimized for overworld.
|
||||
builder_type: FastOverworldBuilder
|
||||
|
||||
# When using the FastOverworldBuilder, should LODs always contain data down to at least sea level?
|
||||
# Enable if your world has floating islands or mountains/structures with large overhangs above sea level.
|
||||
# Optional.
|
||||
# Default: false
|
||||
#scan_to_sea_level: true
|
||||
|
||||
# Limit LOD generation to within this area.
|
||||
# Optional.
|
||||
#border_center: 0,0
|
||||
#border_radius: 5000
|
||||
|
||||
# It might make sense to have a lower view distance in the nether, since visibility will be low simply because of
|
||||
# the level design anyway.
|
||||
world_nether:
|
||||
render_distance: 64
|
||||
|
||||
# Number of worker threads used to generate LODs.
|
||||
# Does not affect Folia servers.
|
||||
# Required.
|
||||
# Default: 4
|
||||
scheduler_threads: 4
|
||||
|
||||
# Don't touch :)
|
||||
config_version: 3
|
||||
@@ -1,4 +1,8 @@
|
||||
name: DHSupport
|
||||
version: 0.2.2
|
||||
main: no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin
|
||||
api-version: 1.21
|
||||
version: ${project.version}
|
||||
main: ${main.class}
|
||||
api-version: ${mc.api-version}
|
||||
mc-version: ${mc.api-version}${mc.api-version-patch}
|
||||
prefix: DHS
|
||||
folia-supported: true
|
||||
author: Jckf
|
||||
Reference in new issue
Block a user