mirror of
https://gitlab.com/distant-horizons-team/distant-horizons-server-plugin.git
synced 2026-11-06 17:32:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0004539b41 | ||
|
|
9ef0f09dd2 | ||
|
|
79cb5aa302 | ||
|
|
5e9760c3d1 | ||
|
|
637636b5e8 | ||
|
|
0ffe620f92 | ||
|
|
2bcf9c7ca9 | ||
|
|
222221f9dc | ||
|
|
d086458546 | ||
|
|
12b1580535 | ||
|
|
81326b415d | ||
|
|
328684cf1d | ||
|
|
d89cfbfec5 | ||
|
|
413e2a6e70 | ||
|
|
ee385ea5af | ||
|
|
37db471c4f | ||
|
|
3e9165b7a3 | ||
|
|
da893ec1f0 | ||
|
|
320c04aeb7 | ||
|
|
fb8f98c52f | ||
|
|
99cee46ae5 | ||
|
|
a05ee01b8b | ||
|
|
b6878aaaaf | ||
|
|
0fbc612eef | ||
|
|
da2dba88b1 | ||
|
|
35822cb8a7 | ||
|
|
9625035edd | ||
|
|
ecda68771c | ||
|
|
f7822bc204 | ||
|
|
ca4a3a6bcc | ||
|
|
a189487623 | ||
|
|
721d4c2027 | ||
|
|
577a45be5c | ||
|
|
8bc102a26f | ||
|
|
8aa9fa9c31 | ||
|
|
3644051706 | ||
|
|
2786f4b8c7 | ||
|
|
930483ee0c | ||
|
|
64b24aba31 | ||
|
|
22447f5bd5 | ||
|
|
697ca18299 | ||
|
|
bef45fe732 | ||
|
|
f60736f21e | ||
|
|
a36102c16e | ||
|
|
ed98a7e0d4 | ||
|
|
1b4138f537 | ||
|
|
a387c7730d | ||
|
|
dbb9d8a2cf | ||
|
|
3c76e8f229 | ||
|
|
967cd2e20d | ||
|
|
83c4a3f6a6 | ||
|
|
c6b89e8794 | ||
|
|
7725a8c0eb | ||
|
|
79cc6e3218 | ||
|
|
15542e3a36 | ||
|
|
275b495675 | ||
|
|
dcf08ba286 | ||
|
|
6532418b5b | ||
|
|
16a68d802f | ||
|
|
f2a40d405d | ||
|
|
c95f54cc0b | ||
|
|
cdeb8f67c9 | ||
|
|
33f374fc41 | ||
|
|
32f78afb39 | ||
|
|
53051b71e3 | ||
|
|
651fd09dde | ||
|
|
a5709d96a4 | ||
|
|
6e11678a80 | ||
|
|
30ea7d5438 | ||
|
|
6c2b77fe25 | ||
|
|
24e2727bf9 | ||
|
|
54ddb357e3 | ||
|
|
7d341959ae | ||
|
|
ca53f19c87 | ||
|
|
4f2a782254 | ||
|
|
e5d4ab61a0 | ||
|
|
375146229e | ||
|
|
95839b85ce | ||
|
|
0243f9bccb | ||
|
|
edfd1df1b9 | ||
|
|
f068a2fa2b | ||
|
|
ffcfded127 | ||
|
|
4cb0d86217 | ||
|
|
5ae9ae65f8 | ||
|
|
c1e0fd8de8 | ||
|
|
6e54fed39e | ||
|
|
a2239bba85 | ||
|
|
f962a0253e | ||
|
|
ac0f4503de | ||
|
|
27680c67ad | ||
|
|
0dacd96ab4 | ||
|
|
50cbb129f8 | ||
|
|
dc47a2a5bb | ||
|
|
0c1db8ea56 | ||
|
|
2215709472 | ||
|
|
659326d0d1 | ||
|
|
2a397afb16 | ||
|
|
0d96af636f | ||
|
|
14c2f69df5 | ||
|
|
4dd995f939 | ||
|
|
f5d2fe1b1d | ||
|
|
70c5b07ed2 | ||
|
|
f7d771edb6 | ||
|
|
48e3f4b7a2 | ||
|
|
fb8f10914d | ||
|
|
10450a8455 | ||
|
|
ade3eeba1b | ||
|
|
040f4f034a | ||
|
|
e183d8fd28 | ||
|
|
9a90ee4378 | ||
|
|
27a9549888 | ||
|
|
c6cfc419b8 | ||
|
|
4eed922a33 | ||
|
|
60062a20f3 | ||
|
|
baf8c526d8 | ||
|
|
78e7486e33 | ||
|
|
c4b5f6c663 | ||
|
|
a5dfdf2102 | ||
|
|
23e0708dbb | ||
|
|
2dac58ad27 | ||
|
|
4d62b2edb8 | ||
|
|
03516559ea | ||
|
|
23062a7662 | ||
|
|
bc94dcff2c | ||
|
|
0da10a0e10 | ||
|
|
a2c33442d5 | ||
|
|
3f18fc17da | ||
|
|
11904d3b30 | ||
|
|
d736b7a5f0 | ||
|
|
b685ad5761 | ||
|
|
31fcb0f6ea | ||
|
|
faa75beb24 | ||
|
|
3596d3879f | ||
|
|
757212a463 | ||
|
|
4b3f28ec9f | ||
|
|
3ef63a184a | ||
|
|
ab5aab6b72 | ||
|
|
ee8b0047e6 | ||
|
|
4dbc8f8a85 | ||
|
|
c97200db03 | ||
|
|
32179bc793 | ||
|
|
29c5fe311d | ||
|
|
3c6f60e8a6 | ||
|
|
e93e633c81 | ||
|
|
29f7193919 | ||
|
|
db41ee0807 | ||
|
|
a694885028 | ||
|
|
c147e07168 | ||
|
|
e54ee9a1b1 | ||
|
|
e48a005c85 | ||
|
|
df3c6f5c78 | ||
|
|
10a64469ef | ||
|
|
0749da55d9 | ||
|
|
dadeb77067 | ||
|
|
2f7d159b19 | ||
|
|
5be954e3fb | ||
|
|
048762b63d | ||
|
|
5fe761d6bc | ||
|
|
c194bf35c9 | ||
|
|
9b06cc0b49 | ||
|
|
0a53d59037 | ||
|
|
3f4ff533ee | ||
|
|
8762ebbafe | ||
|
|
36d884b583 | ||
|
|
2ac627ebce | ||
|
|
3c450482dd | ||
|
|
cc0718cf69 | ||
|
|
1808fd0a92 | ||
|
|
38ea041550 | ||
|
|
6060900428 | ||
|
|
e999cf7ab2 | ||
|
|
4f9e87d262 | ||
|
|
aac5e2540b | ||
|
|
a31fcfd8e4 | ||
|
|
b94fa4b344 | ||
|
|
f962effb40 | ||
|
|
91e2d6445e | ||
|
|
8faf75ff4a | ||
|
|
a58222b4f7 | ||
|
|
6e18444164 | ||
|
|
c9c0277436 | ||
|
|
0443a6a400 | ||
|
|
f55d644144 | ||
|
|
6c0b6ac300 | ||
|
|
8a560dfbc9 | ||
|
|
2d7280440e | ||
|
|
1069b4f623 | ||
|
|
bc15977d11 | ||
|
|
1058dab9b1 | ||
|
|
c72df317c2 | ||
|
|
6d60f906a4 | ||
|
|
6fb8bfc033 | ||
|
|
82333d0e48 | ||
|
|
a5a23ba073 | ||
|
|
4e7ed414ae | ||
|
|
aed02ef99b | ||
|
|
65a66b9a97 | ||
|
|
64709347f9 | ||
|
|
153d072f39 | ||
|
|
cbff7f910b | ||
|
|
307d8795d9 | ||
|
|
41a244013b | ||
|
|
ded98f0237 | ||
|
|
975ac821e2 | ||
|
|
fcbebccd87 | ||
|
|
e26d5812a9 | ||
|
|
fa78e99f64 | ||
|
|
79aabd8b35 | ||
|
|
cdda626cfd | ||
|
|
a41040230c | ||
|
|
7624473148 | ||
|
|
10d73adf59 | ||
|
|
e626d7a6a0 | ||
|
|
61303a7ba1 | ||
|
|
e0da9a0548 | ||
|
|
2bb441d147 | ||
|
|
5ac7913b1e | ||
|
|
d86e281e84 | ||
|
|
495b891b30 | ||
|
|
01857854c8 | ||
|
|
7a8087cb1d | ||
|
|
91d7c849f6 | ||
|
|
2b91ddf437 | ||
|
|
a900b2b628 | ||
|
|
ecf310a8dc | ||
|
|
d1ae74946e | ||
|
|
aca296dbc6 | ||
|
|
d838317503 | ||
|
|
f4c20138aa | ||
|
|
23e0a23406 | ||
|
|
ed371875da | ||
|
|
aee19d166f | ||
|
|
b768f2cd74 | ||
|
|
31b3980509 | ||
|
|
f2421057c2 | ||
|
|
1a2228b230 | ||
|
|
fe7c9b8160 | ||
|
|
dc8bcfd388 | ||
|
|
c096f4b4c0 | ||
|
|
0c89f759d7 | ||
|
|
99e8b8520f | ||
|
|
f74bb49260 | ||
|
|
31ceb3db51 | ||
|
|
aee28bc193 | ||
|
|
e8c24add0b | ||
|
|
11c3cedd03 | ||
|
|
ca0c386917 | ||
|
|
16b993cf91 | ||
|
|
d7385e3268 | ||
|
|
f40cf3ae9f | ||
|
|
3d94ae788a | ||
|
|
7718be6fa7 | ||
|
|
5d164c4ce6 | ||
|
|
d81cab701d | ||
|
|
3bac9955e2 | ||
|
|
179d0dad17 | ||
|
|
d503e18731 | ||
|
|
4bdcdd4736 | ||
|
|
d1bb17d126 | ||
|
|
3677655ec8 | ||
|
|
a82fc18cb5 | ||
|
|
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 | ||
|
|
2fb530de2b | ||
|
|
dbc97a9366 | ||
|
|
e2c0e3f88a | ||
|
|
7a2992d47c | ||
|
|
de03cad4f0 | ||
|
|
bbc26ef9d7 | ||
|
|
fbb07e329d | ||
|
|
e0299eb798 | ||
|
|
5fcd6b8b52 | ||
|
|
32567079f0 | ||
|
|
1c9433489a | ||
|
|
90533f0367 | ||
|
|
3d6c6ab20d | ||
|
|
fa814aa709 | ||
|
|
1b9c5e8269 | ||
|
|
541d0f6991 | ||
|
|
b0a3a903ff | ||
|
|
c7baae63ee | ||
|
|
fcb503d2d2 | ||
|
|
4d6121b27e | ||
|
|
ebf72d7bd8 |
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
|
||||
+5
-3
@@ -25,10 +25,12 @@ replay_pid*
|
||||
|
||||
# IntelliJ IDEA project config
|
||||
/.idea
|
||||
/*.iml
|
||||
|
||||
# Maven output
|
||||
/dependency-reduced-pom.xml
|
||||
/target
|
||||
# Gradle output
|
||||
/.gradle
|
||||
/build
|
||||
|
||||
# Test server
|
||||
/server
|
||||
/folia
|
||||
@@ -0,0 +1,29 @@
|
||||
include:
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
default:
|
||||
image: eclipse-temurin:21
|
||||
|
||||
cache:
|
||||
key: gradle-${CI_COMMIT_REF_SLUG}
|
||||
unprotect: true
|
||||
|
||||
paths:
|
||||
- .gradle
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
||||
script:
|
||||
- ./gradlew build
|
||||
- mv build/libs/*/*.jar ./
|
||||
|
||||
artifacts:
|
||||
name: DistantHorizonsSupport-${CI_COMMIT_REF_SLUG}-${CI_JOB_STARTED_AT}
|
||||
|
||||
paths:
|
||||
- "./*.jar"
|
||||
@@ -1,20 +1,21 @@
|
||||
# DH Support
|
||||
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">
|
||||
|
||||
[Join us on Discord!](https://discord.gg/WzCvvJUf)
|
||||
# Distant Horizons Server Plugin
|
||||
|
||||
## What is it?
|
||||
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.
|
||||
|
||||
DH Support is a Bukkit/Spigot/Paper server plugin that transmits LODs 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.
|
||||
## Installing and configuring
|
||||
|
||||
## How do I use it?
|
||||
Please see [our wiki](https://gitlab.com/distant-horizons-team/distant-horizons-server-plugin/-/wikis/home) for details.
|
||||
|
||||
Just drop the JAR in your plugins folder, forward port 25049 (TCP) to your server, and you're done!
|
||||
## Building
|
||||
|
||||
## Is it configurable?
|
||||
This project uses Gradle.
|
||||
|
||||
Sure! 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.
|
||||
`gradlew clean build`
|
||||
|
||||
## Can I contribute?
|
||||
## Contribution
|
||||
|
||||
Yes! There are several ways to contribute to this project. You can offer your feedback on Discord, 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 ❤️
|
||||
|
||||

|
||||
+107
@@ -0,0 +1,107 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'com.gradleup.shadow' version '8.3.8'
|
||||
}
|
||||
|
||||
group = 'no.jckf.dhs'
|
||||
version = '0.14.0'
|
||||
description = 'Distant Horizons Support'
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
|
||||
def paperVersions = [
|
||||
'1.16.5',
|
||||
]
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url = 'https://jitpack.io/' }
|
||||
}
|
||||
|
||||
configurations {
|
||||
shade
|
||||
implementation {
|
||||
extendsFrom shade
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.jetbrains:annotations:25.0.0'
|
||||
|
||||
implementation 'com.google.guava:guava:33.3.1-jre'
|
||||
|
||||
shade 'org.bstats:bstats-bukkit:3.1.0'
|
||||
shade 'org.json:json:20240303'
|
||||
shade 'org.tukaani:xz:1.10'
|
||||
shade 'com.github.technicallycoded:FoliaLib:v0.4.3'
|
||||
|
||||
testImplementation 'junit:junit:4.11'
|
||||
}
|
||||
|
||||
paperVersions.each { mcVersion ->
|
||||
def taskSuffix = mcVersion.replace('.', '_')
|
||||
def taskName = "buildJar_${taskSuffix}"
|
||||
def outputDir = file("build/libs/$mcVersion")
|
||||
|
||||
def apiVersion = mcVersion.tokenize('.')[0..1].join('.')
|
||||
|
||||
def sourceSetName = "main"
|
||||
|
||||
// Custom processResources task per version
|
||||
def processTask = tasks.register("processResources_${taskSuffix}", Copy) {
|
||||
from(sourceSets[sourceSetName].resources.srcDirs)
|
||||
into("$buildDir/resources-processed/$mcVersion")
|
||||
|
||||
include '**/*.yml'
|
||||
|
||||
expand(
|
||||
version: project.version,
|
||||
'mainClass': 'no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin',
|
||||
'mcApiVersion': apiVersion,
|
||||
)
|
||||
}
|
||||
|
||||
configurations {
|
||||
"compileOnly${mcVersion}" {
|
||||
canBeResolved = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly "com.destroystokyo.paper:paper-api:${mcVersion}-R0.1-SNAPSHOT"
|
||||
//compileOnly "io.papermc.paper:paper-api:${mcVersion}-R0.1-SNAPSHOT"
|
||||
}
|
||||
|
||||
task(taskName, type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
|
||||
group = 'build'
|
||||
|
||||
archiveBaseName.set("DistantHorizonsSupport")
|
||||
archiveVersion.set(version)
|
||||
destinationDirectory.set(outputDir)
|
||||
|
||||
// Use processed resources as input
|
||||
// Exclude raw YAML to avoid unprocessed plugin.yml in final JAR
|
||||
from(sourceSets.main.output) {
|
||||
exclude '**/*.yml'
|
||||
}
|
||||
|
||||
from("$buildDir/resources-processed/$mcVersion")
|
||||
|
||||
configurations = [project.configurations.shade]
|
||||
|
||||
relocate 'org.bstats', 'no.jckf.dhsupport.bstats'
|
||||
relocate 'org.json', 'no.jckf.dhsupport.json'
|
||||
relocate 'org.tukaani.xz', 'no.jckf.dhsupport.xz'
|
||||
relocate 'com.tcoded.folialib', 'no.jckf.dhsupport.folialib'
|
||||
|
||||
exclude 'org/jetbrains/**'
|
||||
exclude 'org/intellij/**'
|
||||
|
||||
dependsOn processTask
|
||||
}
|
||||
|
||||
build.dependsOn(taskName)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,160 +0,0 @@
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>no.jckf</groupId>
|
||||
<name>dh-support</name>
|
||||
<version>0.1.0</version>
|
||||
<artifactId>DHSupport</artifactId>
|
||||
|
||||
<properties>
|
||||
<jdk>17</jdk>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<main.class>no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin</main.class>
|
||||
</properties>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>jckf</id>
|
||||
<name>Jim C K Flaten</name>
|
||||
<email>jckf@jckf.no</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://repo1.maven.org/maven2/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.2.0-jre</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.97.Final</version> <!-- This version number should track the one in the targeted Minecraft server implementation. -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lz4</groupId>
|
||||
<artifactId>lz4-java</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
|
||||
<configuration>
|
||||
<source>${jdk}</source>
|
||||
<target>${jdk}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>all</shadedClassifierName>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.jpountz</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.lz4</shadedPattern>
|
||||
</relocation>
|
||||
|
||||
<relocation>
|
||||
<pattern>org.bstats</pattern>
|
||||
<shadedPattern>no.jckf.dhsupport.bstats</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>org.lz4</include>
|
||||
<include>org.bstats</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<targetPath>.</targetPath>
|
||||
|
||||
<includes>
|
||||
<include>plugin.yml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
<targetPath>.</targetPath>
|
||||
|
||||
<includes>
|
||||
<include>config.yml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
# Distant Horizons Protocol
|
||||
|
||||
Based on information from [here](https://gitlab.com/s809/minecraft-lod-mod/-/wikis/Protocol) and the Distant Horizons source code.
|
||||
|
||||
## Data types
|
||||
|
||||
### Strings
|
||||
Strings are UTF-8 encoded, prefixed by an int containing their length.
|
||||
|
||||
### Short strings
|
||||
Short strings are UTF-8 encoded, prefixed by a short containing their length.
|
||||
|
||||
### Optional fields
|
||||
Optional fields are prefixed by a single byte with value 0 or 1 to signal their presence.
|
||||
|
||||
## Plugin messages
|
||||
|
||||
Common header sent with all messages.
|
||||
* (byte) null
|
||||
* (short) protocol version
|
||||
* (short) packet type
|
||||
|
||||
### 0x0001 Hello
|
||||
|
||||
No additional data.
|
||||
|
||||
### 0x0002 Level key
|
||||
|
||||
* (string) level key
|
||||
* (bool) delete existing data
|
||||
|
||||
### 0x0003 Connect info
|
||||
|
||||
* (bool) address presence
|
||||
* (short) address length
|
||||
* (string) address
|
||||
* (short) port
|
||||
|
||||
## Socket messages
|
||||
|
||||
Common header sent with all messages.
|
||||
* (int) length
|
||||
* (short) packet type
|
||||
|
||||
Additional header data for trackable messages.
|
||||
* (int) tracker
|
||||
|
||||
### 0x0001 Hello
|
||||
|
||||
* (int) protocol version
|
||||
|
||||
### 0x0002 Close reason
|
||||
|
||||
* (short) reason length
|
||||
* (string) reason
|
||||
|
||||
### 0x0003 Ack (trackable)
|
||||
|
||||
No additional data.
|
||||
|
||||
### 0x0004 Cancel (trackable)
|
||||
|
||||
No additional data.
|
||||
|
||||
### 0x0005 Exception (trackable)
|
||||
|
||||
* (int) exception type
|
||||
* (short) message length
|
||||
* (string) message
|
||||
|
||||
### 0x0006 Player UUID (trackable)
|
||||
|
||||
* (long) UUID most significant bits
|
||||
* (long) UUID least significant bits
|
||||
|
||||
### 0x0007 Player config
|
||||
|
||||
* (int) render distance
|
||||
* (bool) distant generation enabled
|
||||
* (int) full data request concurrency limit
|
||||
* (int) generation task priority request rate limit
|
||||
* (bool) real time updates enabled
|
||||
* (bool) login data sync enabled
|
||||
* (int) login data sync rate/concurrency limit
|
||||
* (bool) generate multiple dimensions
|
||||
|
||||
### 0x0008 Full data request (trackable)
|
||||
|
||||
* (short string) level name
|
||||
* (byte) detail level
|
||||
* (int) x coordinate
|
||||
* (int) z coordinate
|
||||
* (bool) checksum presence
|
||||
* (int) checksum
|
||||
|
||||
### 0x0009 Full data response (trackable)
|
||||
|
||||
This message contains compressed data. Each of the entries below suffixed with `(compressed)` start with a single uncompressed int denoting the length in bytes of the compressed data. Everything else is compressed according to the `compression type` field.
|
||||
|
||||
* (bool) is full
|
||||
* position
|
||||
* (byte) detail level
|
||||
* (int) x coordinate
|
||||
* (int) z coordinate
|
||||
* (int) checksum
|
||||
* LOD (compressed)
|
||||
* (int) compressed data length
|
||||
* for each column
|
||||
* (short) number of data points
|
||||
* for each data point
|
||||
* (long) data point
|
||||
* column generation steps (compressed)
|
||||
* (int) compressed data length
|
||||
* for each column
|
||||
* (byte) generation step (0: Empty. 1: Structure start. 2: Structure reference. 3: Biomes. 4: Noise. 5: Surface. 6: Carvers. 7: Liquid carvers. 8: Features. 9: Light.)
|
||||
* column compression types (compressed)
|
||||
* (int) compressed data length
|
||||
* for each column
|
||||
* (byte) compression type (0: Strict. 1: Loose.)
|
||||
* mappings (compressed)
|
||||
* (int) compressed data length
|
||||
* (int) number of mappings
|
||||
* for each mapping
|
||||
* (short string) concatenated string containing biome, block type, and block state
|
||||
* (byte) data format version
|
||||
* (byte) compression type (0: None. 1: LZ4. 2: ZSTD. 3: LZMA.)
|
||||
* (bool) apply to parent
|
||||
* (long) last modified timestamp
|
||||
* (long) created timestamp
|
||||
|
||||
### 0x000a Partial update
|
||||
|
||||
* (short string) level name
|
||||
* (int) x coordinate
|
||||
* (int) z coordinate
|
||||
* (int) data length
|
||||
* data
|
||||
|
||||
### 0x000b Generation task priority request (trackable)
|
||||
|
||||
* (short string) level name
|
||||
* (int) position list length
|
||||
* position list
|
||||
|
||||
### 0x000c Generation task priority response (trackable)
|
||||
|
||||
* (int) position list length
|
||||
* position list
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'DistantHorizonsSupport'
|
||||
@@ -19,6 +19,7 @@
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import no.jckf.dhsupport.core.message.plugin.PluginMessageSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -35,6 +36,12 @@ public class BukkitPluginMessageSender implements PluginMessageSender
|
||||
@Override
|
||||
public void sendPluginMessage(UUID recipientUuid, String channel, byte[] message)
|
||||
{
|
||||
this.plugin.getServer().getPlayer(recipientUuid).sendPluginMessage(this.plugin, channel, message);
|
||||
Player player = this.plugin.getServer().getPlayer(recipientUuid);
|
||||
|
||||
if (player == null || !player.isOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendPluginMessage(this.plugin, channel, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BukkitScheduler implements Scheduler
|
||||
{
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
protected FoliaLib foliaLib;
|
||||
|
||||
protected ExecutorService executor;
|
||||
|
||||
protected ScheduledExecutorService scheduledExecutor;
|
||||
|
||||
public BukkitScheduler(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
|
||||
this.foliaLib = new FoliaLib(this.plugin);
|
||||
|
||||
int threadCount = this.plugin.getDhSupport().getConfig().getInt(DhsConfig.SCHEDULER_THREADS);
|
||||
|
||||
this.executor = new ThreadPoolExecutor(
|
||||
threadCount, threadCount,
|
||||
60, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactory() {
|
||||
private final java.util.concurrent.atomic.AtomicInteger threadNumber = new java.util.concurrent.atomic.AtomicInteger(1);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r)
|
||||
{
|
||||
return new Thread(r, "DHSupport-Worker-" + threadNumber.getAndIncrement());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
((ThreadPoolExecutor) this.executor).allowCoreThreadTimeOut(true);
|
||||
|
||||
this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "DHSupport-Scheduler"));
|
||||
|
||||
this.plugin.getDhSupport().info("Using " + Utils.ucFirst(this.foliaLib.getImplType().name().toLowerCase().replace('_', ' ')) + " scheduler.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReadWorldAsync()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnMainThread(Supplier<U> supplier)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
if (this.foliaLib.getScheduler().isGlobalTickThread()) {
|
||||
future.complete(supplier.get());
|
||||
} else {
|
||||
this.foliaLib.getScheduler().runNextTick((task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnMainThreadDelayed(Supplier<U> supplier, long delayTicks)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
this.foliaLib.getScheduler().runLater((task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}, delayTicks);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnMainThreadDelayed(Supplier<U> supplier, long delayAmount, TimeUnit delayUnit)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
this.foliaLib.getScheduler().runLater((task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}, delayAmount, delayUnit);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
if (this.foliaLib.getScheduler().isOwnedByCurrentRegion(location)) {
|
||||
future.complete(supplier.get());
|
||||
} else {
|
||||
this.foliaLib.getScheduler().runAtLocation(
|
||||
location,
|
||||
(task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnRegionThreadDelayed(UUID worldId, int x, int z, Supplier<U> supplier, long delayTicks)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
Location location = new Location(
|
||||
this.plugin.getServer().getWorld(worldId),
|
||||
x,
|
||||
0,
|
||||
z
|
||||
);
|
||||
|
||||
this.foliaLib.getScheduler().runAtLocationLater(location, (task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}, delayTicks);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnRegionThreadDelayed(UUID worldId, int x, int z, Supplier<U> supplier, long delayAmount, TimeUnit delayUnit)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
Location location = new Location(
|
||||
this.plugin.getServer().getWorld(worldId),
|
||||
x,
|
||||
0,
|
||||
z
|
||||
);
|
||||
|
||||
this.foliaLib.getScheduler().runAtLocationLater(location, (task) -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
}, delayAmount, delayUnit);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnSeparateThreadDelayed(Supplier<U> supplier, long delayTicks)
|
||||
{
|
||||
return this.runOnSeparateThreadDelayed(supplier, delayTicks * 50L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> CompletableFuture<U> runOnSeparateThreadDelayed(Supplier<U> supplier, long delayAmount, TimeUnit delayUnit)
|
||||
{
|
||||
CompletableFuture<U> future = new CompletableFuture<>();
|
||||
|
||||
this.scheduledExecutor.schedule(() -> {
|
||||
this.runOnSeparateThread(() -> {
|
||||
try {
|
||||
future.complete(supplier.get());
|
||||
} catch (Exception exception) {
|
||||
future.completeExceptionally(exception);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}, delayAmount, delayUnit);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelTasks()
|
||||
{
|
||||
this.scheduledExecutor.shutdownNow();
|
||||
this.executor.shutdownNow();
|
||||
this.foliaLib.getScheduler().cancelAllTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getExecutor()
|
||||
{
|
||||
return this.executor;
|
||||
}
|
||||
|
||||
public void runTimer(Runnable runnable, long initialDelay, long interval)
|
||||
{
|
||||
this.foliaLib.getScheduler().runTimer(runnable, initialDelay, interval);
|
||||
}
|
||||
}
|
||||
@@ -18,38 +18,470 @@
|
||||
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.configuration.WorldConfiguration;
|
||||
import no.jckf.dhsupport.core.scheduling.Scheduler;
|
||||
import no.jckf.dhsupport.core.world.MaterialRecord;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
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 boolean dummyMode = false;
|
||||
|
||||
protected Map<Integer, String> dummyColumn;
|
||||
|
||||
protected boolean trustHeightMap = true;
|
||||
|
||||
private Integer sectionX;
|
||||
private Integer sectionZ;
|
||||
|
||||
private final ChunkSnapshot[] chunks = new ChunkSnapshot[16];
|
||||
|
||||
protected EnumMap<Material, MaterialRecord> materialCache = new EnumMap<>(Material.class);
|
||||
|
||||
protected Map<String, Map<String, String>> propertyCache = new HashMap<>();
|
||||
|
||||
protected UnsafeValues unsafeValues;
|
||||
|
||||
@Nullable
|
||||
protected Method getCoordinateScale;
|
||||
|
||||
@Nullable
|
||||
protected Method getBiomeKey;
|
||||
|
||||
@Nullable
|
||||
protected Method getChunkAtAsync;
|
||||
|
||||
protected int lightOffset = 0;
|
||||
|
||||
public BukkitWorldInterface(DhSupportBukkitPlugin plugin, World world, Configuration config)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.world = world;
|
||||
this.config = config;
|
||||
this.worldConfig = new WorldConfiguration(this, config);
|
||||
|
||||
this.lightOffset = this.getConfig().getInt(DhsConfig.LIGHT_OFFSET_HACK, this.plugin.getDhSupport().getGameVersion().equals("1.20.1") ? this.world.getMinHeight() : 0);
|
||||
this.trustHeightMap = this.getConfig().getBool(DhsConfig.TRUST_HEIGHT_MAP, this.trustHeightMap);
|
||||
}
|
||||
|
||||
public void setLogger(Logger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Logger getLogger()
|
||||
{
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
public void doUnsafeThings()
|
||||
{
|
||||
this.unsafeValues = Bukkit.getUnsafe();
|
||||
|
||||
// Does not exist for 1.16.5.
|
||||
try {
|
||||
this.getCoordinateScale = this.world.getClass().getMethod("getCoordinateScale");
|
||||
} catch (NoSuchMethodException e) {
|
||||
|
||||
}
|
||||
|
||||
// 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("getChunkAtAsync", int.class, int.class, boolean.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 newInstance = new BukkitWorldInterface(this.plugin, this.world, this.config);
|
||||
|
||||
newInstance.setLogger(this.getLogger());
|
||||
|
||||
newInstance.doUnsafeThings();
|
||||
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
int sectionX = Coordinates.chunkToSection(chunkX);
|
||||
int sectionZ = Coordinates.chunkToSection(chunkZ);
|
||||
|
||||
if (this.sectionX == null) {
|
||||
this.sectionX = sectionX;
|
||||
this.sectionZ = sectionZ;
|
||||
} else if (this.sectionX != sectionX || this.sectionZ != sectionZ) {
|
||||
throw new IllegalStateException("We appear to be leaking world interface instances across LOD sections");
|
||||
}
|
||||
|
||||
return this.block;
|
||||
int baseChunkX = Coordinates.sectionToChunk(sectionX);
|
||||
int baseChunkZ = Coordinates.sectionToChunk(sectionZ);
|
||||
|
||||
int index = (chunkX - baseChunkX) + ((chunkZ - baseChunkZ) << 2);
|
||||
|
||||
ChunkSnapshot chunk = this.chunks[index];
|
||||
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
Scheduler scheduler = this.plugin.getDhSupport().getScheduler();
|
||||
|
||||
if (scheduler.canReadWorldAsync()) {
|
||||
chunk = this.world.getChunkAt(chunkX, chunkZ).getChunkSnapshot(true, true, false);
|
||||
} else {
|
||||
chunk = scheduler.runOnRegionThread(world.getUID(), x, z, () ->
|
||||
this.world.getChunkAt(chunkX, chunkZ).getChunkSnapshot(true, true, false)
|
||||
).join();
|
||||
}
|
||||
|
||||
this.chunks[index] = chunk;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public boolean isInsideSection(int x, int z)
|
||||
{
|
||||
return Coordinates.blockToSection(x) == this.sectionX && Coordinates.blockToSection(z) == this.sectionZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId()
|
||||
{
|
||||
return this.world.getUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return this.world.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey()
|
||||
{
|
||||
return this.getName().replaceAll("[^a-zA-Z0-9_-]", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCoordinateScale()
|
||||
{
|
||||
if (this.getCoordinateScale == null) {
|
||||
boolean isNether = this.world.getEnvironment().equals(World.Environment.NETHER);
|
||||
|
||||
return isNether ? 8 : 1;
|
||||
}
|
||||
|
||||
try {
|
||||
return (double) this.getCoordinateScale.invoke(this.world);
|
||||
} catch (InvocationTargetException | IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean useVanillaWorldBorder()
|
||||
{
|
||||
return this.worldConfig.getBool(DhsConfig.USE_VANILLA_WORLD_BORDER, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDummyMode(boolean enable)
|
||||
{
|
||||
this.dummyMode = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDummyMode()
|
||||
{
|
||||
return this.dummyMode;
|
||||
}
|
||||
|
||||
protected Map<Integer, String> buildDummyColumn()
|
||||
{
|
||||
int minY = this.getMinY();
|
||||
|
||||
Map<Integer, String> column = new HashMap<>();
|
||||
|
||||
for (String spec : this.getConfig().getStringList(DhsConfig.DUMMY_CHUNK)) {
|
||||
String[] components = spec.toLowerCase().split(",", 2);
|
||||
|
||||
String namespacedKey = Utils.namespaceKey(components[1]);
|
||||
|
||||
if (namespacedKey.equals("minecraft:air") || namespacedKey.equals("minecraft:void_air")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] range = components[0].split("-", 2);
|
||||
|
||||
int rangeStart = Integer.parseInt(range[0]);
|
||||
int rangeEnd = range.length == 1 ? rangeStart : Integer.parseInt(range[1]);
|
||||
|
||||
for (int i = rangeStart; i <= rangeEnd; i++) {
|
||||
column.put(minY + i, namespacedKey);
|
||||
}
|
||||
}
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
protected Map<Integer, String> getDummyColumn()
|
||||
{
|
||||
if (this.dummyColumn == null) {
|
||||
this.dummyColumn = this.buildDummyColumn();
|
||||
}
|
||||
|
||||
return this.dummyColumn;
|
||||
}
|
||||
|
||||
protected String getDummyMaterialAt(int y)
|
||||
{
|
||||
return this.getDummyColumn().getOrDefault(y, "minecraft:air");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getWorldBorderX()
|
||||
{
|
||||
if (this.useVanillaWorldBorder()) {
|
||||
return this.world.getWorldBorder().getCenter().getBlockX();
|
||||
}
|
||||
|
||||
return this.worldConfig.getInt(DhsConfig.BORDER_CENTER_X);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getWorldBorderZ()
|
||||
{
|
||||
if (this.useVanillaWorldBorder()) {
|
||||
return this.world.getWorldBorder().getCenter().getBlockZ();
|
||||
}
|
||||
|
||||
return this.worldConfig.getInt(DhsConfig.BORDER_CENTER_Z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getWorldBorderRadius()
|
||||
{
|
||||
if (!this.useVanillaWorldBorder()) {
|
||||
return this.worldConfig.getInt(DhsConfig.BORDER_RADIUS);
|
||||
}
|
||||
|
||||
int borderRadius = (int) (this.world.getWorldBorder().getSize() / 2.0);
|
||||
|
||||
if (this.worldConfig.getString(DhsConfig.VANILLA_WORLD_BORDER_EXPANSION, "auto").equals("auto")) {
|
||||
borderRadius += Coordinates.chunkToBlock(this.world.getViewDistance());
|
||||
} else {
|
||||
borderRadius += Coordinates.chunkToBlock(this.worldConfig.getInt(DhsConfig.VANILLA_WORLD_BORDER_EXPANSION, 0));
|
||||
}
|
||||
|
||||
return borderRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chunkExists(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 isChunkLoaded(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.isChunkLoaded(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadChunk(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.loadChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadOrGenerateChunk(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.loadChunk(chunkX, chunkZ, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> loadChunkAsync(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
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, false);
|
||||
|
||||
return chunkFuture.thenApply(Objects::nonNull);
|
||||
} catch (InvocationTargetException | IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> loadOrGenerateChunkAsync(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
if (this.getChunkAtAsync == null) {
|
||||
return this.plugin.getDhSupport().getScheduler().runOnMainThread(() -> this.loadOrGenerateChunk(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, true);
|
||||
|
||||
return chunkFuture.thenApply(Objects::nonNull);
|
||||
} catch (InvocationTargetException | IllegalAccessException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unloadChunk(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.unloadChunk(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unloadChunkAsync(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.unloadChunkRequest(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean discardChunk(int x, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int chunkX = Coordinates.blockToChunk(x);
|
||||
int chunkZ = Coordinates.blockToChunk(z);
|
||||
|
||||
return this.world.unloadChunk(chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,72 +499,316 @@ public class BukkitWorldInterface implements WorldInterface
|
||||
@Override
|
||||
public int getSeaLevel()
|
||||
{
|
||||
return this.world.getSeaLevel();
|
||||
return Math.min(Math.max(this.getMinY(), this.world.getSeaLevel()), this.getMaxY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnX()
|
||||
{
|
||||
return this.world.getSpawnLocation().getBlockX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnY()
|
||||
{
|
||||
return this.world.getSpawnLocation().getBlockY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnZ()
|
||||
{
|
||||
return this.world.getSpawnLocation().getBlockZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHighestYAt(int x, int z)
|
||||
{
|
||||
return this.world.getHighestBlockAt(x, z).getY();
|
||||
if (!this.trustHeightMap) {
|
||||
return this.getMaxY() - 1;
|
||||
}
|
||||
|
||||
if (this.isDummyMode()) {
|
||||
return this.getDummyColumn().isEmpty() ? this.getMinY() : Collections.max(this.getDummyColumn().keySet());
|
||||
}
|
||||
|
||||
return this.getChunk(x, z).getHighestBlockYAt(Coordinates.blockToChunkRelative(x), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiomeAt(int x, int y, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return "minecraft:plains";
|
||||
}
|
||||
|
||||
NamespacedKey key = this.getChunk(x, z).getBiome(Coordinates.blockToChunkRelative(x), y, 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, y, z);
|
||||
} catch (IllegalAccessException | InvocationTargetException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBiomeAt(int x, int z)
|
||||
{
|
||||
return this.getBlock(x, this.world.getSeaLevel(), z).getBiome().getKey().toString();
|
||||
return this.getBiomeAt(x, this.getSeaLevel(), z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaterialAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getMaterial().getKey().toString();
|
||||
if (this.isDummyMode()) {
|
||||
return Utils.namespaceKey(this.getDummyMaterialAt(y));
|
||||
}
|
||||
|
||||
return this.getMaterialRecordAt(x, y, z).toString();
|
||||
}
|
||||
|
||||
public MaterialRecord getMaterialRecordAt(int x, int y, int z)
|
||||
{
|
||||
return this.materialCache.computeIfAbsent(
|
||||
this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)),
|
||||
(mat) -> {
|
||||
String key = Utils.unNamespaceKey(mat.getKey().toString());
|
||||
String mapped = this.config.getString(DhsConfig.MATERIAL_MAP + '.' + key, key);
|
||||
|
||||
return new MaterialRecord(
|
||||
Utils.namespaceKey(mapped == null ? key : mapped),
|
||||
!key.equals(mapped)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBlockStateAsStringAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getAsString();
|
||||
MaterialRecord materialRecord = this.getMaterialRecordAt(x, y, z);
|
||||
|
||||
if (this.isDummyMode() || materialRecord.isMapped()) {
|
||||
return materialRecord.toString();
|
||||
}
|
||||
|
||||
Map<String, String> properties = this.getBlockPropertiesAt(x, y, z);
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
return materialRecord.toString();
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(materialRecord.toString())
|
||||
.append('[');
|
||||
|
||||
for (Map.Entry<String, String> property : properties.entrySet()) {
|
||||
builder.append(property.getKey())
|
||||
.append('=')
|
||||
.append(property.getValue())
|
||||
.append(',');
|
||||
}
|
||||
|
||||
builder.setLength(builder.length() - 1);
|
||||
builder.append(']');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getBlockPropertiesAt(int x, int y, int z)
|
||||
{
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
if (this.isDummyMode()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String dataString = this.getBlockStateAsStringAt(x, y, z);
|
||||
String dataString = this.getChunk(x, z).getBlockData(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).getAsString();
|
||||
|
||||
int kvStart = dataString.indexOf("[");
|
||||
Map<String, String> cached = this.propertyCache.get(dataString);
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
int kvStart = dataString.indexOf('[');
|
||||
|
||||
if (kvStart == -1) {
|
||||
return properties;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String[] kvStrings = dataString.substring(kvStart + 1, dataString.length() - 1).split(",");
|
||||
|
||||
Arrays.sort(kvStrings);
|
||||
|
||||
Map<String, String> properties = new LinkedHashMap<>(kvStrings.length);
|
||||
|
||||
for (String kvString : kvStrings) {
|
||||
String[] kv = kvString.split("=", 2);
|
||||
|
||||
properties.put(kv[0], kv[1]);
|
||||
}
|
||||
|
||||
properties = Collections.unmodifiableMap(properties);
|
||||
|
||||
this.propertyCache.put(dataString, properties);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getBlockLightAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getLightFromBlocks();
|
||||
if (this.isDummyMode()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (byte) this.getChunk(x, z).getBlockEmittedLight(Coordinates.blockToChunkRelative(x), Math.clamp(y + this.lightOffset, this.getMinY(), this.getMaxY() - 1), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getSkyLightAt(int x, int y, int z)
|
||||
{
|
||||
return this.getBlock(x, y, z).getLightFromSky();
|
||||
if (this.isDummyMode()) {
|
||||
return (byte) (this.getDummyMaterialAt(y + 1).equals("minecraft:air") ? 15 : 0);
|
||||
}
|
||||
|
||||
return (byte) this.getChunk(x, z).getBlockSkyLight(Coordinates.blockToChunkRelative(x), Math.clamp(y + this.lightOffset, this.getMinY(), this.getMaxY() - 1), Coordinates.blockToChunkRelative(z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransparent(int x, int y, int z)
|
||||
public Configuration getConfig()
|
||||
{
|
||||
return this.getBlock(x, y, z).getBlockData().getMaterial().isTransparent();
|
||||
return this.worldConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeacon(int x, int y, int z)
|
||||
{
|
||||
if (this.isDummyMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getChunk(x, z).getBlockType(Coordinates.blockToChunkRelative(x), y, Coordinates.blockToChunkRelative(z)).equals(Material.BEACON)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.hasTierOneBeaconBase(x, y, z) && this.hasClearBeaconBeamPath(x, y, z);
|
||||
}
|
||||
|
||||
public boolean hasTierOneBeaconBase(int x, int y, int z)
|
||||
{
|
||||
int baseY = y - 1;
|
||||
|
||||
if (baseY < this.getMinY()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int checkX = x - 1; checkX <= x + 1; checkX++) {
|
||||
for (int checkZ = z - 1; checkZ <= z + 1; checkZ++) {
|
||||
if (this.isInsideSection(checkX, checkZ) && !this.isBeaconBaseBlock(this.getMaterialAt(checkX, baseY, checkZ))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isBeaconBaseBlock(String material)
|
||||
{
|
||||
switch (material) {
|
||||
case "minecraft:iron_block":
|
||||
case "minecraft:gold_block":
|
||||
case "minecraft:diamond_block":
|
||||
case "minecraft:emerald_block":
|
||||
case "minecraft:netherite_block":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasClearBeaconBeamPath(int x, int y, int z)
|
||||
{
|
||||
for (int checkY = y + 1; checkY < this.getMaxY(); checkY++) {
|
||||
String material = this.getMaterialAt(x, checkY, z);
|
||||
|
||||
switch (material) {
|
||||
case "minecraft:air":
|
||||
case "minecraft:cave_air":
|
||||
case "minecraft:void_air":
|
||||
case "minecraft:glass":
|
||||
continue;
|
||||
default:
|
||||
return material.endsWith("_stained_glass");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBeaconColor(int x, int y, int z)
|
||||
{
|
||||
int skipped = 0;
|
||||
int n = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
int maxY = this.getMaxY();
|
||||
|
||||
while (y + 1 < maxY) {
|
||||
y++;
|
||||
|
||||
String material = Utils.unNamespaceKey(this.getMaterialAt(x, y, z)).toUpperCase();
|
||||
|
||||
if (!material.endsWith("_STAINED_GLASS") && !material.endsWith("_STAINED_GLASS_PANE")) {
|
||||
skipped++;
|
||||
|
||||
if (skipped >= 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
n++;
|
||||
|
||||
Color color = null;
|
||||
|
||||
for (DyeColor testColor : DyeColor.values()) {
|
||||
if (material.startsWith(testColor.name())) {
|
||||
color = testColor.getColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (color == null) {
|
||||
this.getLogger().warning("Encountered unknown color: " + material);
|
||||
continue;
|
||||
}
|
||||
|
||||
red += color.getRed();
|
||||
green += color.getGreen();
|
||||
blue += color.getBlue();
|
||||
|
||||
if (n > 1) {
|
||||
red /= 2;
|
||||
green /= 2;
|
||||
blue /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
return Color.WHITE.asRGB();
|
||||
}
|
||||
|
||||
return (new java.awt.Color(red, green, blue)).getRGB();
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,25 @@
|
||||
|
||||
package no.jckf.dhsupport.bukkit;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.handler.*;
|
||||
import no.jckf.dhsupport.bukkit.commands.DhCommand;
|
||||
import no.jckf.dhsupport.bukkit.commands.DhsCommand;
|
||||
import no.jckf.dhsupport.bukkit.handler.ConfigLoader;
|
||||
import no.jckf.dhsupport.bukkit.handler.PlayerHandler;
|
||||
import no.jckf.dhsupport.bukkit.handler.PluginMessageProxy;
|
||||
import no.jckf.dhsupport.bukkit.handler.WorldHandler;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.MultiLineChart;
|
||||
import org.bstats.charts.SingleLineChart;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -33,69 +46,137 @@ public class DhSupportBukkitPlugin extends JavaPlugin
|
||||
|
||||
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()
|
||||
{
|
||||
this.dhSupport = new DhSupport();
|
||||
String pluginVersion = "N/A";
|
||||
|
||||
try {
|
||||
YamlConfiguration meta = new YamlConfiguration();
|
||||
meta.load(new InputStreamReader(this.getResource("plugin.yml")));
|
||||
|
||||
pluginVersion = meta.getString("version");
|
||||
} catch (Exception exception) {
|
||||
|
||||
}
|
||||
|
||||
String gameVersion = this.getGameVersion();
|
||||
|
||||
this.getLogger().info("I am DHS " + pluginVersion + " running on " + this.getServer().getName() + " for Minecraft " + gameVersion + ".");
|
||||
|
||||
this.dhSupport = new DhSupport(pluginVersion, gameVersion);
|
||||
this.dhSupport.setLogger(this.getLogger());
|
||||
this.dhSupport.setDataDirectory(this.getDataFolder().getAbsolutePath());
|
||||
|
||||
this.metrics = new Metrics(this, 21843);
|
||||
this.metrics.addCustomChart(new SingleLineChart("dh_users", () -> this.dhSupport.getPlayerConfigurations().size()));
|
||||
this.metrics.addCustomChart(new MultiLineChart("dh_render_distance", () -> {
|
||||
Map<String, Integer> renderDistances = new HashMap<>();
|
||||
|
||||
for (Class<? extends Handler> className : this.handlers.keySet()) {
|
||||
try {
|
||||
Handler instance = className
|
||||
.getConstructor(DhSupportBukkitPlugin.class)
|
||||
.newInstance(this);
|
||||
for (Configuration playerConfig : this.dhSupport.getPlayerConfigurations().values()) {
|
||||
String rd = String.valueOf(playerConfig.getInt(DhsConfig.RENDER_DISTANCE));
|
||||
|
||||
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;
|
||||
renderDistances.put(rd, renderDistances.getOrDefault(rd, 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return renderDistances;
|
||||
}));
|
||||
|
||||
this.loadDhsConfig();
|
||||
|
||||
this.pluginMessageProxy = new PluginMessageProxy(this);
|
||||
this.pluginMessageProxy.onEnable();
|
||||
|
||||
this.scheduler = new BukkitScheduler(this);
|
||||
this.dhSupport.setScheduler(this.scheduler);
|
||||
|
||||
this.dhSupport.onEnable();
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(new WorldHandler(this), this);
|
||||
this.getServer().getPluginManager().registerEvents(new PlayerPresenceHandler(this), this);
|
||||
int lodRefreshInterval = this.getDhSupport().getConfig().getInt(DhsConfig.LOD_REFRESH_INTERVAL) * 20;
|
||||
|
||||
this.getLogger().info("Ready 😀");
|
||||
this.scheduler.runTimer(() -> {
|
||||
this.dhSupport.getGenerationTracker().prune();
|
||||
this.dhSupport.updateTouchedLods();
|
||||
}, lodRefreshInterval, lodRefreshInterval);
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(new WorldHandler(this), this);
|
||||
this.getServer().getPluginManager().registerEvents(new PlayerHandler(this), this);
|
||||
|
||||
this.getCommand("dhs").setExecutor(new DhsCommand(this));
|
||||
this.getCommand("dh").setExecutor(new DhCommand(this));
|
||||
|
||||
this.getLogger().info("Ready!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
if (this.metrics != null) {
|
||||
this.metrics.shutdown();
|
||||
}
|
||||
|
||||
if (this.scheduler != null) {
|
||||
this.scheduler.cancelTasks();
|
||||
}
|
||||
|
||||
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);
|
||||
this.getLogger().info("Lights out!");
|
||||
}
|
||||
|
||||
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);
|
||||
public void loadDhsConfig()
|
||||
{
|
||||
if (this.configLoader != null) {
|
||||
this.configLoader.onDisable();
|
||||
}
|
||||
|
||||
this.getLogger().info("👋😭");
|
||||
this.configLoader = new ConfigLoader(this);
|
||||
this.configLoader.onEnable();
|
||||
}
|
||||
|
||||
public @Nullable World getWorld(String name)
|
||||
{
|
||||
for (World world : this.getServer().getWorlds()) {
|
||||
if (world.getName().replace(' ', '_').equals(name)) {
|
||||
return world;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getGameVersion()
|
||||
{
|
||||
try {
|
||||
Class<? extends Server> serverClass = this.getServer().getClass();
|
||||
|
||||
serverClass.getMethod("getMinecraftVersion");
|
||||
|
||||
return this.getServer().getMinecraftVersion();
|
||||
} catch (NoSuchMethodException exception) {
|
||||
// If we're here, that probably means we're not running Paper or any Paper fork.
|
||||
}
|
||||
|
||||
return this.getServer().getBukkitVersion().split("-", 2)[0];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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.bukkit.commands;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DhCommand implements CommandExecutor
|
||||
{
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
public DhCommand(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args)
|
||||
{
|
||||
if (!this.plugin.getDhSupport().getConfig().getBool(DhsConfig.ENABLE_DH_COMMAND_HELP_TEXT, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "You have issued a " + ChatColor.YELLOW + "/dh" + ChatColor.RED + " command which is meant for the Distant Horizons " + ChatColor.YELLOW + "mod" + ChatColor.RED + ", but you are using the " + ChatColor.YELLOW + "plugin" + ChatColor.RED + ". Please make sure you are reading the correct documentation.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* 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.commands;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.Coordinates;
|
||||
import no.jckf.dhsupport.core.PreGenerator;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DhsCommand implements CommandExecutor
|
||||
{
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
public DhsCommand(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Missing sub-command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "status":
|
||||
return this.status(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "reload":
|
||||
return this.reload(sender);
|
||||
|
||||
case "worlds":
|
||||
return this.worlds(sender);
|
||||
|
||||
case "pregen":
|
||||
return this.pregen(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "pause":
|
||||
return this.pause(sender);
|
||||
|
||||
case "unpause":
|
||||
return this.unpause(sender);
|
||||
|
||||
case "trim":
|
||||
return this.trim(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "bench":
|
||||
return this.bench(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "config":
|
||||
return this.config(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sub-command.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean status(CommandSender sender, String[] args)
|
||||
{
|
||||
if (args.length == 0) {
|
||||
return this.statusServer(sender);
|
||||
}
|
||||
|
||||
return this.statusPlayer(sender, args[0]);
|
||||
}
|
||||
|
||||
protected boolean statusServer(CommandSender sender)
|
||||
{
|
||||
Configuration config;
|
||||
|
||||
if (sender instanceof Player) {
|
||||
World world = ((Player) sender).getWorld();
|
||||
|
||||
config = this.plugin.getDhSupport().getWorldInterface(world.getUID()).getConfig();
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Distant Horizons Support status for world " + ChatColor.GREEN + world.getName() + ChatColor.BLUE + ":");
|
||||
} else {
|
||||
config = this.plugin.getDhSupport().getConfig();
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Distant Horizons Support status for " + ChatColor.GREEN + "global context" + ChatColor.BLUE + ":");
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Distant generation is " + (config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED) ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.BLUE + " (" + ChatColor.GREEN + config.getInt(DhsConfig.RENDER_DISTANCE) + ChatColor.BLUE + ").");
|
||||
sender.sendMessage(ChatColor.BLUE + "Real time updates is " + (config.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED) ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.BLUE + " (" + ChatColor.GREEN + config.getInt(DhsConfig.REAL_TIME_UPDATE_RADIUS) + ChatColor.BLUE + ").");
|
||||
sender.sendMessage(ChatColor.BLUE + "Builder type is " + ChatColor.GREEN + config.getString(DhsConfig.BUILDER_TYPE) + ChatColor.BLUE + ".");
|
||||
|
||||
int playerCount = this.plugin.getDhSupport().getPlayerConfigurations().size();
|
||||
|
||||
StringBuilder playerList = new StringBuilder();
|
||||
|
||||
for (UUID playerId : this.plugin.getDhSupport().getPlayerConfigurations().keySet()) {
|
||||
playerList.append(ChatColor.GREEN + this.plugin.getServer().getPlayer(playerId).getName() + ChatColor.BLUE + ", ");
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "There " + (playerCount == 1 ? "is" : "are") + " " + ChatColor.GREEN + playerCount + ChatColor.BLUE + " " + (playerCount == 1 ? "player" : "players") + " online using Distant Horizons" + (playerCount == 0 ? "." : ": " + playerList.substring(0, playerList.length() - 2)));
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Current generation speed: " + ChatColor.GREEN + String.format("%.2f", this.plugin.getDhSupport().getGenerationTracker().getPingsPerSecond() * 16) + " CPS");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean statusPlayer(CommandSender sender, String playerName)
|
||||
{
|
||||
Player player = this.plugin.getServer().getPlayer(playerName);
|
||||
|
||||
if (player == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Player not found.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Configuration config = this.plugin.getDhSupport().getPlayerConfiguration(player.getUniqueId());
|
||||
|
||||
if (config == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "The player " + ChatColor.WHITE + player.getName() + ChatColor.YELLOW + " is not using Distant Horizons.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Distant Horizons Support status for player " + ChatColor.GREEN + player.getName() + ChatColor.BLUE + ":");
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Distant generation is " + (config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED) ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.BLUE + " (" + ChatColor.GREEN + config.getInt(DhsConfig.RENDER_DISTANCE) + ChatColor.BLUE + ").");
|
||||
sender.sendMessage(ChatColor.BLUE + "Real time updates is " + (config.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED) ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled") + ChatColor.BLUE + " (" + ChatColor.GREEN + config.getInt(DhsConfig.REAL_TIME_UPDATE_RADIUS) + ChatColor.BLUE + ").");
|
||||
sender.sendMessage(ChatColor.BLUE + "Server has sent " + ChatColor.GREEN + config.getInt("buffer-id", 0) + ChatColor.BLUE + " LODs to them.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean reload(CommandSender sender)
|
||||
{
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloading config...");
|
||||
|
||||
this.plugin.loadDhsConfig();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "Reload complete.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean worlds(CommandSender sender)
|
||||
{
|
||||
sender.sendMessage(ChatColor.GREEN + "World list:");
|
||||
|
||||
for (World bukkitWorld : this.plugin.getServer().getWorlds()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + " - " + ChatColor.GREEN + bukkitWorld.getName() + " " + ChatColor.GRAY + "(" + bukkitWorld.getUID() + ")");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean pregen(CommandSender sender, String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Missing action.");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "start":
|
||||
return this.pregenStart(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "stop":
|
||||
return this.pregenStop(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
case "status":
|
||||
return this.pregenStatus(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Unknown action.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean pregenStart(CommandSender sender, String[] args)
|
||||
{
|
||||
WorldInterface world;
|
||||
Integer centerX;
|
||||
Integer centerZ;
|
||||
Integer radius;
|
||||
|
||||
if (args.length >= 1) {
|
||||
World bukkitWorld = this.plugin.getWorld(args[0]);
|
||||
|
||||
if (bukkitWorld == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown world.");
|
||||
return true;
|
||||
}
|
||||
|
||||
world = this.plugin.getDhSupport().getWorldInterface(bukkitWorld.getUID());
|
||||
} else if (sender instanceof Player) {
|
||||
world = this.plugin.getDhSupport().getWorldInterface(((Player) sender).getWorld().getUID());
|
||||
} else {
|
||||
world = null;
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No world specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 3) {
|
||||
centerX = Integer.parseInt(args[1]);
|
||||
centerZ = Integer.parseInt(args[2]);
|
||||
} else {
|
||||
centerX = world.getWorldBorderX();
|
||||
centerZ = world.getWorldBorderZ();
|
||||
}
|
||||
|
||||
if (centerX == null || centerZ == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No center coordinates specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 4) {
|
||||
radius = Coordinates.chunkToBlock(Integer.parseInt(args[3]));
|
||||
} else {
|
||||
radius = world.getWorldBorderRadius();
|
||||
}
|
||||
|
||||
if (radius == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No radius specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean force = args.length >= 5 && args[4].equals("force");
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "Generating LODs for view distance of " + ChatColor.GREEN + Coordinates.blockToChunk(radius) + ChatColor.YELLOW + " chunks in world " + ChatColor.GREEN + world.getName() + ChatColor.YELLOW + " starting at center " + ChatColor.GREEN + centerX + " " + centerZ + ChatColor.YELLOW + "...");
|
||||
|
||||
if (force) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "All existing LODs in this area will be re-generated.");
|
||||
}
|
||||
|
||||
this.plugin.getDhSupport().preGenerate(world, centerX, centerZ, radius, force);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean pregenStop(CommandSender sender, String[] args)
|
||||
{
|
||||
WorldInterface world;
|
||||
|
||||
if (args.length >= 1) {
|
||||
World bukkitWorld = this.plugin.getWorld(args[0]);
|
||||
|
||||
if (bukkitWorld == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown world.");
|
||||
return true;
|
||||
}
|
||||
|
||||
world = this.plugin.getDhSupport().getWorldInterface(bukkitWorld.getUID());
|
||||
} else if (sender instanceof Player) {
|
||||
world = this.plugin.getDhSupport().getWorldInterface(((Player) sender).getWorld().getUID());
|
||||
} else {
|
||||
world = null;
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No world specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.plugin.getDhSupport().isPreGenerating(world)) {
|
||||
sender.sendMessage(ChatColor.RED + "No pre-generator running in world " + ChatColor.YELLOW + world.getName() + ChatColor.RED + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "Stopping pre-generation in world " + ChatColor.GREEN + world.getName() + ChatColor.YELLOW + "...");
|
||||
|
||||
this.plugin.getDhSupport().stopPreGenerator(world);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean pregenStatus(CommandSender sender, String[] args)
|
||||
{
|
||||
WorldInterface world;
|
||||
|
||||
if (args.length >= 1) {
|
||||
World bukkitWorld = this.plugin.getWorld(args[0]);
|
||||
|
||||
if (bukkitWorld == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown world.");
|
||||
return true;
|
||||
}
|
||||
|
||||
world = this.plugin.getDhSupport().getWorldInterface(bukkitWorld.getUID());
|
||||
} else if (sender instanceof Player) {
|
||||
world = this.plugin.getDhSupport().getWorldInterface(((Player) sender).getWorld().getUID());
|
||||
} else {
|
||||
world = null;
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No world specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
PreGenerator generator = this.plugin.getDhSupport().getPreGenerator(world);
|
||||
|
||||
if (generator == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No pre-generator running in world " + ChatColor.YELLOW + world.getName() + ChatColor.RED + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
Duration elapsedTime = generator.getElapsedTime();
|
||||
|
||||
float momentaryLodsPerSecond = (float) this.plugin.getDhSupport().getGenerationTracker().getPingsPerSecond();
|
||||
float totalLodsPerSecond = (float) generator.getCompletedRequests() / elapsedTime.toSeconds();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "Generation progress: " + ChatColor.YELLOW + String.format("%.2f", generator.getProgress() * 100f) + "%");
|
||||
sender.sendMessage(ChatColor.GREEN + "Processed LODs: " + ChatColor.YELLOW + generator.getCompletedRequests() + ChatColor.GREEN + " / " + ChatColor.YELLOW + generator.getTargetRequests() + ChatColor.GREEN + " (" + ChatColor.YELLOW + String.format("%.2f", totalLodsPerSecond * 16) + ChatColor.GREEN + " CPS)");
|
||||
sender.sendMessage(ChatColor.GREEN + "Time elapsed: " + ChatColor.YELLOW + Utils.humanReadableDuration(elapsedTime));
|
||||
|
||||
if (generator.isRunning()) {
|
||||
sender.sendMessage(ChatColor.GREEN + "Time remaining: " + ChatColor.YELLOW + Utils.humanReadableDuration(Duration.ofSeconds((long) ((generator.getTargetRequests() - generator.getCompletedRequests()) / momentaryLodsPerSecond))));
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.GREEN + "Generation is complete.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean pause(CommandSender sender)
|
||||
{
|
||||
if (this.plugin.getDhSupport().isPaused()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already paused.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.plugin.getDhSupport().pause()) {
|
||||
sender.sendMessage(ChatColor.RED + "Could not pause. Check server log for error messages.");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "DHS has been paused.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean unpause(CommandSender sender)
|
||||
{
|
||||
if (!this.plugin.getDhSupport().isPaused()) {
|
||||
sender.sendMessage(ChatColor.RED + "Not paused.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.plugin.getDhSupport().unpause()) {
|
||||
sender.sendMessage(ChatColor.RED + "Could not unpause. Check server log for error messages.");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "DHS has been unpaused.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean trim(CommandSender sender, String[] args)
|
||||
{
|
||||
WorldInterface world;
|
||||
Integer centerX;
|
||||
Integer centerZ;
|
||||
Integer radius;
|
||||
|
||||
if (args.length >= 1) {
|
||||
World bukkitWorld = this.plugin.getWorld(args[0]);
|
||||
|
||||
if (bukkitWorld == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown world.");
|
||||
return true;
|
||||
}
|
||||
|
||||
world = this.plugin.getDhSupport().getWorldInterface(bukkitWorld.getUID());
|
||||
} else if (sender instanceof Player) {
|
||||
world = this.plugin.getDhSupport().getWorldInterface(((Player) sender).getWorld().getUID());
|
||||
} else {
|
||||
world = null;
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No world specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 3) {
|
||||
centerX = Integer.parseInt(args[1]);
|
||||
centerZ = Integer.parseInt(args[2]);
|
||||
} else {
|
||||
centerX = world.getWorldBorderX();
|
||||
centerZ = world.getWorldBorderZ();
|
||||
}
|
||||
|
||||
if (centerX == null || centerZ == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No center coordinates specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 4) {
|
||||
radius = Integer.parseInt(args[3]);
|
||||
} else {
|
||||
radius = world.getWorldBorderRadius();
|
||||
}
|
||||
|
||||
if (radius == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No radius specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.YELLOW + "Trimming LODs outside view distance of " + ChatColor.GREEN + radius + ChatColor.YELLOW + " blocks in world " + ChatColor.GREEN + world.getName() + ChatColor.YELLOW + " centered at " + ChatColor.GREEN + centerX + " " + centerZ + ChatColor.YELLOW + "...");
|
||||
|
||||
this.plugin.getDhSupport().trim(world, centerX, centerZ, radius)
|
||||
.thenAccept((trimmedCount) -> {
|
||||
sender.sendMessage(ChatColor.GREEN + "Trimming of " + ChatColor.YELLOW + trimmedCount + ChatColor.GREEN + " LODs in " + ChatColor.YELLOW + world.getName() + ChatColor.GREEN + " completed.");
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean bench(CommandSender sender, String[] args)
|
||||
{
|
||||
int schedulerThreads = this.plugin.getDhSupport().getConfig().getInt(DhsConfig.SCHEDULER_THREADS);
|
||||
|
||||
WorldInterface world;
|
||||
int iterations = 1000;
|
||||
int concurrency = schedulerThreads;
|
||||
|
||||
if (args.length >= 1) {
|
||||
World bukkitWorld = this.plugin.getWorld(args[0]);
|
||||
|
||||
if (bukkitWorld == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown world.");
|
||||
return true;
|
||||
}
|
||||
|
||||
world = this.plugin.getDhSupport().getWorldInterface(bukkitWorld.getUID());
|
||||
} else if (sender instanceof Player) {
|
||||
world = this.plugin.getDhSupport().getWorldInterface(((Player) sender).getWorld().getUID());
|
||||
} else {
|
||||
world = null;
|
||||
}
|
||||
|
||||
if (world == null) {
|
||||
sender.sendMessage(ChatColor.RED + "No world specified.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 2) {
|
||||
iterations = Integer.parseInt(args[1]);
|
||||
}
|
||||
|
||||
if (args.length >= 3) {
|
||||
concurrency = Integer.parseInt(args[2]);
|
||||
}
|
||||
|
||||
if (concurrency > schedulerThreads) {
|
||||
sender.sendMessage(ChatColor.RED + "Concurrency of " + ChatColor.YELLOW + concurrency + ChatColor.RED + " is more than the " + ChatColor.YELLOW + schedulerThreads + ChatColor.RED + " scheduler threads this server is running.");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.BLUE + "Starting benchmark with " + ChatColor.GREEN + iterations + ChatColor.BLUE + " iterations times " + ChatColor.GREEN + concurrency + ChatColor.BLUE + " threads in world " + ChatColor.GREEN + world.getName() + ChatColor.BLUE + "...");
|
||||
|
||||
this.plugin.getDhSupport().benchmark(world.getId(), iterations, concurrency)
|
||||
.thenAccept((lodsPerSecond) -> {
|
||||
sender.sendMessage(ChatColor.GREEN + "Benchmark completed! Result: " + ChatColor.BLUE + String.format("%.2f", lodsPerSecond * 16) + ChatColor.GREEN + " CPS.");
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean config(CommandSender sender, String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Missing action.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Missing key.");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "set":
|
||||
if (args.length < 3) {
|
||||
sender.sendMessage(ChatColor.RED + "Missing value.");
|
||||
return true;
|
||||
}
|
||||
|
||||
this.plugin.getDhSupport().getConfig().set(args[1], args[2]);
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "Set " + ChatColor.BLUE + args[1] + ChatColor.GREEN + " to " + ChatColor.BLUE + args[2] + ChatColor.GREEN + ".");
|
||||
return true;
|
||||
|
||||
case "get":
|
||||
sender.sendMessage(ChatColor.GREEN + "Value of " + ChatColor.BLUE + args[1] + ChatColor.GREEN + " is " + ChatColor.BLUE + this.plugin.getDhSupport().getConfig().get(args[1]) + ChatColor.GREEN + ".");
|
||||
return true;
|
||||
|
||||
case "unset":
|
||||
this.plugin.getDhSupport().getConfig().unset(args[1]);
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "Unset " + ChatColor.BLUE + args[1] + ChatColor.GREEN + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Unknown action.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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,22 +37,43 @@ 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 bundledConfigVersion = 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 (!bundledConfigVersion.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();
|
||||
|
||||
// Get rid of potentially old config values in the case of a reload.
|
||||
dhsConfig.clear();
|
||||
|
||||
// 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
|
||||
public void onDisable()
|
||||
{
|
||||
|
||||
// Revert any changes we did.
|
||||
this.plugin.reloadConfig();
|
||||
}
|
||||
}
|
||||
+26
-5
@@ -19,29 +19,50 @@
|
||||
package no.jckf.dhsupport.bukkit.handler;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public class PlayerPresenceHandler implements Listener
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PlayerHandler implements Listener
|
||||
{
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
public PlayerPresenceHandler(DhSupportBukkitPlugin plugin)
|
||||
public PlayerHandler(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent join)
|
||||
public void onPlayerConnect(PlayerJoinEvent event)
|
||||
{
|
||||
Player player = event.getPlayer();
|
||||
|
||||
DhSupport dhSupport = this.plugin.getDhSupport();
|
||||
|
||||
dhSupport.getScheduler().runOnMainThreadDelayed(() -> {
|
||||
if (dhSupport.getPlayerConfiguration(player.getUniqueId()) != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (String message : dhSupport.getConfig().getStringList(DhsConfig.AD_MESSAGE, Collections.emptyList())) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, 10, TimeUnit.SECONDS); // Arbitrary delay, waiting for DH config message (or the lack of one).
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerLeave(PlayerQuitEvent quit)
|
||||
public void onPlayerDisconnect(PlayerQuitEvent event)
|
||||
{
|
||||
this.plugin.getDhSupport().closeAndForgetByUuid(quit.getPlayer().getUniqueId());
|
||||
this.plugin.getDhSupport().clearPlayerConfiguration(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package no.jckf.dhsupport.bukkit.handler;
|
||||
import no.jckf.dhsupport.bukkit.BukkitPluginMessageSender;
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.handler.PluginMessageHandler;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -55,6 +56,10 @@ public class PluginMessageProxy extends Handler implements PluginMessageListener
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message)
|
||||
{
|
||||
this.pluginMessageHandler.onPluginMessageReceived(channel, player.getUniqueId(), message);
|
||||
try {
|
||||
this.pluginMessageHandler.onPluginMessageReceived(channel, player.getUniqueId(), message);
|
||||
} catch (RuntimeException exception) {
|
||||
player.sendMessage(ChatColor.RED + "Distant Horizons: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,28 +20,51 @@ package no.jckf.dhsupport.bukkit.handler;
|
||||
|
||||
import no.jckf.dhsupport.bukkit.BukkitWorldInterface;
|
||||
import no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WorldHandler implements Listener
|
||||
{
|
||||
protected static final String[] EVENT_METHOD_NAMES = { "blockList", "getBlocks", "getBlock", "getLocation", "getChunk" };
|
||||
|
||||
protected DhSupportBukkitPlugin plugin;
|
||||
|
||||
protected Map<String, Method> eventMethods = new HashMap<>();
|
||||
|
||||
public WorldHandler(DhSupportBukkitPlugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
|
||||
this.plugin.getServer().getWorlds().forEach(this::addWorldInterface);
|
||||
|
||||
this.registerUpdateListeners();
|
||||
}
|
||||
|
||||
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 +72,96 @@ public class WorldHandler implements Listener
|
||||
this.plugin.getDhSupport().setWorldInterface(world.getUID(), null);
|
||||
}
|
||||
|
||||
protected void touchLod(Location location, String reason)
|
||||
{
|
||||
this.plugin.getDhSupport().touchLod(location.getWorld().getUID(), location.getBlockX(), location.getBlockZ(), reason);
|
||||
}
|
||||
|
||||
protected void registerUpdateListeners()
|
||||
{
|
||||
Listener dummyListener = new Listener() {};
|
||||
|
||||
for (String eventClassName : this.plugin.getDhSupport().getConfig().getStringList(DhsConfig.UPDATE_EVENTS)) {
|
||||
try {
|
||||
Class<? extends Event> eventClass = Class.forName(eventClassName).asSubclass(Event.class);
|
||||
|
||||
this.plugin.getServer().getPluginManager().registerEvent(
|
||||
eventClass,
|
||||
dummyListener,
|
||||
EventPriority.MONITOR,
|
||||
(listener, event) -> this.handleUpdateEvent(event),
|
||||
this.plugin
|
||||
);
|
||||
|
||||
this.plugin.getDhSupport().info("Listening for " + eventClassName + ".");
|
||||
} catch (ClassNotFoundException exception) {
|
||||
this.plugin.getDhSupport().warning("Could not find event class: " + eventClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleUpdateEvent(Event event)
|
||||
{
|
||||
Class<? extends Event> eventClass = event.getClass();
|
||||
String eventClassName = eventClass.getName();
|
||||
Method method = null;
|
||||
|
||||
if (this.eventMethods.containsKey(eventClassName)) {
|
||||
method = this.eventMethods.get(eventClassName);
|
||||
|
||||
// We've seen this event before, but don't know how to handle it.
|
||||
if (method == null) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
for (String methodName : EVENT_METHOD_NAMES) {
|
||||
try {
|
||||
method = eventClass.getMethod(methodName);
|
||||
break;
|
||||
} catch (NoSuchMethodException exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Store the result, even if it is null.
|
||||
this.eventMethods.put(eventClassName, method);
|
||||
|
||||
// This is the first time we see this even, and we don't know how to handle it.
|
||||
if (method == null) {
|
||||
this.plugin.getDhSupport().warning("Unsure how to handle event: " + eventClass.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<?> list;
|
||||
|
||||
try {
|
||||
Object result = method.invoke(event);
|
||||
|
||||
if (result instanceof Location location) {
|
||||
list = List.of(location.getBlock());
|
||||
} else if (result instanceof Chunk chunk) {
|
||||
list = List.of(chunk.getBlock(0, 0, 0));
|
||||
} else if (result instanceof List) {
|
||||
list = (List<?>) result;
|
||||
} else {
|
||||
list = List.of(result);
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
|
||||
for (Object entry : list) {
|
||||
if (entry instanceof Block block) {
|
||||
this.touchLod(block.getLocation(), eventClassName);
|
||||
} else if (entry instanceof BlockState state) {
|
||||
this.touchLod(state.getLocation(), eventClassName);
|
||||
} else {
|
||||
this.plugin.getDhSupport().warning("Unknown block " + entry + " from event: " + eventClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent worldLoad)
|
||||
{
|
||||
@@ -60,16 +173,4 @@ public class WorldHandler implements Listener
|
||||
{
|
||||
this.removeWorldInterface(worldUnload.getWorld());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkLoad(ChunkLoadEvent chunkLoad)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkUnload(ChunkUnloadEvent chunkUnload)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 java.util.function.BiConsumer;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// PACK/UNPACK
|
||||
|
||||
public static long pack(int x, int z)
|
||||
{
|
||||
return ((long) x & 0xffffffffL)
|
||||
| (((long) z & 0xffffffffL) << 32);
|
||||
}
|
||||
|
||||
public static int unpackX(long packed)
|
||||
{
|
||||
return (int) packed;
|
||||
}
|
||||
|
||||
public static int unpackZ(long packed)
|
||||
{
|
||||
return (int) (packed >> 32);
|
||||
}
|
||||
|
||||
// ???
|
||||
|
||||
public static void foreachChunkInSection(int sectionX, int sectionZ, BiConsumer<Integer, Integer> consumer)
|
||||
{
|
||||
int worldX = sectionToBlock(sectionX);
|
||||
int worldZ = sectionToBlock(sectionZ);
|
||||
|
||||
for (int xMultiplier = 0; xMultiplier < 4; xMultiplier++) {
|
||||
for (int zMultiplier = 0; zMultiplier < 4; zMultiplier++) {
|
||||
consumer.accept(
|
||||
worldX + 16 * xMultiplier,
|
||||
worldZ + 16 * zMultiplier
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,98 +18,206 @@
|
||||
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
import io.netty.channel.ChannelId;
|
||||
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.AddLodVersionColumn;
|
||||
import no.jckf.dhsupport.core.database.migrations.CreateLodsTable;
|
||||
import no.jckf.dhsupport.core.database.migrations.DeleteLodsWithoutVersion;
|
||||
import no.jckf.dhsupport.core.database.models.LodModel;
|
||||
import no.jckf.dhsupport.core.database.repositories.AsyncLodRepository;
|
||||
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.handler.SocketMessageHandler;
|
||||
import no.jckf.dhsupport.core.handler.message.PluginHandshakeHandler;
|
||||
import no.jckf.dhsupport.core.handler.message.SocketHandshakeHandler;
|
||||
import no.jckf.dhsupport.core.handler.message.SocketLodHandler;
|
||||
import no.jckf.dhsupport.core.lodbuilders.LodBuilder;
|
||||
import no.jckf.dhsupport.core.message.plugin.FullDataChunkMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.FullDataPartialUpdateMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.PluginMessageSender;
|
||||
import no.jckf.dhsupport.core.scheduling.Scheduler;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
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.concurrent.ExecutionException;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class DhSupport implements Configurable
|
||||
{
|
||||
protected String pluginVersion;
|
||||
|
||||
protected String gameVersion;
|
||||
|
||||
protected String dataDirectory;
|
||||
|
||||
protected Database database;
|
||||
|
||||
protected AsyncLodRepository lodRepository;
|
||||
|
||||
protected Configuration configuration;
|
||||
|
||||
protected Logger logger;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
|
||||
protected UpdateChecker updateChecker;
|
||||
|
||||
@Nullable
|
||||
protected CompletableFuture<?> pause;
|
||||
|
||||
protected PerformanceTracker generationTracker = new PerformanceTracker();
|
||||
|
||||
protected Map<UUID, WorldInterface> worldInterfaces = new HashMap<>();
|
||||
|
||||
protected PluginMessageHandler pluginMessageHandler;
|
||||
|
||||
protected SocketMessageHandler socketMessageHandler;
|
||||
|
||||
protected PluginMessageSender pluginMessageSender;
|
||||
|
||||
protected Map<ChannelId, UUID> socketToUuidMap = new HashMap<>();
|
||||
protected Map<String, CompletableFuture<Lod>> queuedBuilders = new ConcurrentHashMap<>();
|
||||
|
||||
protected Map<UUID, ChannelId> uuidToSocketMap = new HashMap<>();
|
||||
protected Map<String, LodModel> touchedLods = new ConcurrentHashMap<>();
|
||||
|
||||
protected ExecutorService executor;
|
||||
protected Map<UUID, Configuration> playerConfigurations = new HashMap<>();
|
||||
|
||||
protected Map<UUID, Map<String, byte[]>> lodCache = new HashMap<>();
|
||||
protected Map<UUID, PreGenerator> preGenerators = new HashMap<>();
|
||||
|
||||
public DhSupport()
|
||||
public DhSupport(String pluginVersion, String gameVersion)
|
||||
{
|
||||
// Mumble mumble. Something about passing references to an incomplete "this".
|
||||
this.pluginVersion = pluginVersion;
|
||||
this.gameVersion = gameVersion;
|
||||
|
||||
this.configuration = new Configuration();
|
||||
|
||||
this.database = new Database();
|
||||
this.lodRepository = new AsyncLodRepository(this.database);
|
||||
|
||||
this.pluginMessageHandler = new PluginMessageHandler(this);
|
||||
this.socketMessageHandler = new SocketMessageHandler(this);
|
||||
|
||||
this.updateChecker = new UpdateChecker(62013887);
|
||||
}
|
||||
|
||||
public void onEnable()
|
||||
{
|
||||
this.executor = Executors.newFixedThreadPool(4);
|
||||
this.lodRepository.setLogger(this.getLogger());
|
||||
|
||||
// TODO: Store these instances somewhere?
|
||||
// References will exist inside the event bus, so they won't be GCed, but this _is_ a little bit ugly.
|
||||
(new PluginHandshakeHandler(this, this.pluginMessageHandler)).register();
|
||||
try {
|
||||
this.database.open(
|
||||
this.getConfig().getString(DhsConfig.DATABASE_PATH)
|
||||
.replace("{datadir}", this.getDataDirectory())
|
||||
);
|
||||
|
||||
(new SocketHandshakeHandler(this, this.socketMessageHandler)).register();
|
||||
(new SocketLodHandler(this, this.socketMessageHandler)).register();
|
||||
this.database.addMigration(CreateLodsTable.class);
|
||||
this.database.addMigration(AddLodVersionColumn.class);
|
||||
this.database.addMigration(DeleteLodsWithoutVersion.class);
|
||||
|
||||
this.database.migrate();
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to initialize database!", exception);
|
||||
}
|
||||
|
||||
(new PlayerConfigHandler(this, this.pluginMessageHandler)).register();
|
||||
(new LodHandler(this, this.pluginMessageHandler)).register();
|
||||
|
||||
this.pluginMessageHandler.onEnable();
|
||||
this.socketMessageHandler.onEnable();
|
||||
|
||||
if (this.getConfig().getBool(DhsConfig.GENERATE_NEW_CHUNKS, true) && this.getConfig().getBool(DhsConfig.GENERATE_NEW_CHUNKS_WARNING, true)) {
|
||||
this.warning("Chunk generation is enabled. New chunks will be generated as needed to complete LOD generation. This could significantly increase the size of your world.");
|
||||
this.warning("If you understand what this means and would like to disable this warning, set " + DhsConfig.GENERATE_NEW_CHUNKS_WARNING + " to false in your config.");
|
||||
}
|
||||
|
||||
if (this.getConfig().getBool(DhsConfig.CHECK_FOR_UPDATES, true)) {
|
||||
this.getScheduler().runOnSeparateThread(() -> {
|
||||
this.checkUpdates();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable()
|
||||
{
|
||||
if (this.socketMessageHandler != null) {
|
||||
this.socketMessageHandler.onDisable();
|
||||
}
|
||||
|
||||
if (this.pluginMessageHandler != null) {
|
||||
this.pluginMessageHandler.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
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 boolean pause()
|
||||
{
|
||||
if (this.pause != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.pause = new CompletableFuture<>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unpause()
|
||||
{
|
||||
if (this.pause == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.pause.complete(null);
|
||||
this.pause = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isPaused()
|
||||
{
|
||||
return this.pause != null;
|
||||
}
|
||||
|
||||
public void joinPauseState()
|
||||
{
|
||||
if (this.pause != null) {
|
||||
this.pause.join();
|
||||
}
|
||||
}
|
||||
|
||||
public PerformanceTracker getGenerationTracker()
|
||||
{
|
||||
return this.generationTracker;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -118,18 +226,11 @@ public class DhSupport implements Configurable
|
||||
return this.worldInterfaces.get(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PluginMessageHandler getPluginMessageHandler()
|
||||
{
|
||||
return this.pluginMessageHandler;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SocketMessageHandler getSocketMessageHandler()
|
||||
{
|
||||
return this.socketMessageHandler;
|
||||
}
|
||||
|
||||
public void setPluginMessageSender(PluginMessageSender sender)
|
||||
{
|
||||
this.pluginMessageSender = sender;
|
||||
@@ -141,13 +242,23 @@ public class DhSupport implements Configurable
|
||||
return this.pluginMessageSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncLodRepository getLodRepository()
|
||||
{
|
||||
return this.lodRepository;
|
||||
}
|
||||
|
||||
public String getPluginVersion()
|
||||
{
|
||||
return this.pluginVersion;
|
||||
}
|
||||
|
||||
public String getGameVersion()
|
||||
{
|
||||
return this.gameVersion;
|
||||
}
|
||||
|
||||
public Configuration getConfig()
|
||||
{
|
||||
if (this.configuration == null) {
|
||||
this.configuration = new Configuration();
|
||||
}
|
||||
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
@@ -172,72 +283,435 @@ public class DhSupport implements Configurable
|
||||
this.getLogger().warning(message);
|
||||
}
|
||||
|
||||
public void associatePlayerAndSocket(UUID playerUuid, ChannelId socketId)
|
||||
public void debug(String message)
|
||||
{
|
||||
this.socketToUuidMap.put(socketId, playerUuid);
|
||||
this.uuidToSocketMap.put(playerUuid, socketId);
|
||||
|
||||
//this.info("Associated " + socketId + "/" + playerUuid);
|
||||
}
|
||||
|
||||
public void closeAndForgetByUuid(UUID playerUuid)
|
||||
{
|
||||
this.closeAndForgetBySocketId(this.getSocketIdByPlayerUuid(playerUuid));
|
||||
}
|
||||
|
||||
public void closeAndForgetBySocketId(ChannelId id)
|
||||
{
|
||||
try {
|
||||
this.socketMessageHandler.getSocketServer().getSocket(id).close();
|
||||
} catch (Exception exception) {
|
||||
// Whatever. It was probably closed already.
|
||||
// TODO: Use log levels properly?
|
||||
if (!this.getConfig().getBool(DhsConfig.DEBUG, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = this.socketToUuidMap.remove(id);
|
||||
this.uuidToSocketMap.remove(uuid);
|
||||
|
||||
//this.info("Forgot " + id + "/" + uuid);
|
||||
this.getLogger().info("[DEBUG] " + message);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ChannelId getSocketIdByPlayerUuid(UUID uuid)
|
||||
public void checkUpdates()
|
||||
{
|
||||
return this.uuidToSocketMap.get(uuid);
|
||||
if (this.updateChecker.isLatestVersion(this.pluginVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.warning("A newer version of the plugin is available.");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UUID getPlayerUuidBySocketId(ChannelId id)
|
||||
public Map<UUID, Configuration> getPlayerConfigurations()
|
||||
{
|
||||
return this.socketToUuidMap.get(id);
|
||||
return this.playerConfigurations;
|
||||
}
|
||||
|
||||
public CompletableFuture<Lod> queueBuilder(LodBuilder builder)
|
||||
public void setPlayerConfiguration(UUID playerId, Configuration playerConfiguration)
|
||||
{
|
||||
return CompletableFuture.supplyAsync(builder::generate, this.executor);
|
||||
this.playerConfigurations.put(playerId, playerConfiguration);
|
||||
}
|
||||
|
||||
public CompletableFuture<byte[]> getLodData(UUID worldId, SectionPosition position)
|
||||
public Configuration getPlayerConfiguration(UUID playerId)
|
||||
{
|
||||
String key = position.getX() + "x" + position.getZ();
|
||||
return this.playerConfigurations.get(playerId);
|
||||
}
|
||||
|
||||
if (!this.lodCache.get(worldId).containsKey(key)) {
|
||||
//this.info("Cache miss: " + worldId + " " + key);
|
||||
public void clearPlayerConfiguration(UUID playerId)
|
||||
{
|
||||
this.playerConfigurations.remove(playerId);
|
||||
}
|
||||
|
||||
LodBuilder builder = new LodBuilder(this.getWorldInterface(worldId).newInstance(), position);
|
||||
public LodBuilder getBuilder(WorldInterface world, SectionPosition position)
|
||||
{
|
||||
String builderType = world.getConfig().getString(DhsConfig.BUILDER_TYPE);
|
||||
|
||||
return this.queueBuilder(builder).thenApply((lod) -> {
|
||||
Encoder encoder = new Encoder();
|
||||
lod.encode(encoder);
|
||||
byte[] data = encoder.toByteArray();
|
||||
LodBuilder builder;
|
||||
|
||||
this.lodCache.get(worldId).put(key, data);
|
||||
try {
|
||||
Class<? extends LodBuilder> builderClass = Class.forName(LodBuilder.class.getPackageName() + "." + builderType).asSubclass(LodBuilder.class);
|
||||
|
||||
return data;
|
||||
Constructor<?> constructor = builderClass.getConstructor(WorldInterface.class, SectionPosition.class);
|
||||
|
||||
builder = (LodBuilder) constructor.newInstance(world, position);
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | ClassNotFoundException exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
CompletableFuture<Lod> queued = this.getScheduler().runOnSeparateThread(() -> {
|
||||
this.joinPauseState();
|
||||
|
||||
return builder.generate();
|
||||
})
|
||||
.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)
|
||||
{
|
||||
return this.getLodRepository()
|
||||
.loadLodAsync(worldId, position.getX(), position.getZ())
|
||||
.thenComposeAsync((modelFromDb) -> {
|
||||
// If an LOD was found in the database, return it.
|
||||
if (modelFromDb != null) {
|
||||
return CompletableFuture.completedFuture(modelFromDb);
|
||||
}
|
||||
|
||||
// Otherwise generate a new one.
|
||||
return this.generateLod(worldId, position, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected CompletableFuture<LodModel> generateLod(UUID worldId, SectionPosition position, boolean manageChunks)
|
||||
{
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
WorldInterface world = this.getWorldInterface(worldId).newInstance();
|
||||
|
||||
boolean generateNewChunks = world.getConfig().getBool(DhsConfig.GENERATE_NEW_CHUNKS, true);
|
||||
|
||||
Map<Long, CompletableFuture<Boolean>> loads = new HashMap<>();
|
||||
|
||||
if (manageChunks) {
|
||||
// Load all the chunks we need for this request.
|
||||
Coordinates.foreachChunkInSection(position.getX(), position.getZ(), (chunkX, chunkZ) -> {
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
if (generateNewChunks) {
|
||||
loads.put(Coordinates.pack(chunkX, chunkZ), world.loadOrGenerateChunkAsync(chunkX, chunkZ));
|
||||
} else {
|
||||
loads.put(Coordinates.pack(chunkX, chunkZ), world.loadChunkAsync(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//this.info("Cache hit: " + worldId + " " + key);
|
||||
// Wait for chunk loads, then...
|
||||
return CompletableFuture.allOf(loads.values().toArray(new CompletableFuture[0]))
|
||||
.thenComposeAsync((asd) -> {
|
||||
boolean loadRejected = loads
|
||||
.values()
|
||||
.stream()
|
||||
.map(loadRequest -> {
|
||||
try {
|
||||
return loadRequest.get();
|
||||
} catch (InterruptedException | ExecutionException exception) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.anyMatch(Predicate.isEqual(false));
|
||||
|
||||
return CompletableFuture.completedFuture(this.lodCache.get(worldId).get(key));
|
||||
if (loadRejected) {
|
||||
// Use dummy chunks instead of real world data.
|
||||
world.setDummyMode(true);
|
||||
} else {
|
||||
// Prime chunk data in world interface.
|
||||
Coordinates.foreachChunkInSection(position.getX(), position.getZ(), (chunkX, chunkZ) -> {
|
||||
world.getMaterialAt(chunkX, world.getSeaLevel(), chunkZ);
|
||||
});
|
||||
}
|
||||
|
||||
// Discard the chunks we loaded.
|
||||
this.getScheduler().runOnRegionThread(worldId, worldX, worldZ, () -> {
|
||||
for (long packed : loads.keySet()) {
|
||||
world.discardChunk(
|
||||
Coordinates.unpackX(packed),
|
||||
Coordinates.unpackZ(packed)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// No LOD was found. Start building a new one.
|
||||
CompletableFuture<Lod> lodFuture = this.queueBuilder(worldId, position, this.getBuilder(world, position));
|
||||
|
||||
// Combine the LOD and beacons and save the result in the database.
|
||||
return lodFuture.thenApply((lod) -> {
|
||||
Encoder lodEncoder = new Encoder(8192);
|
||||
lod.encode(lodEncoder);
|
||||
|
||||
Encoder beaconEncoder = new Encoder(16 + lod.getBeacons().size() * 16);
|
||||
beaconEncoder.writeCollection(lod.getBeacons());
|
||||
|
||||
this.generationTracker.ping();
|
||||
|
||||
return this.lodRepository.saveLodAsync(
|
||||
worldId,
|
||||
position.getX(),
|
||||
position.getZ(),
|
||||
lodEncoder.toByteArray(),
|
||||
beaconEncoder.toByteArray(),
|
||||
LodModel.VERSION
|
||||
);
|
||||
})
|
||||
.thenCompose((f) -> f); // Unwrap the nested future.
|
||||
});
|
||||
}
|
||||
|
||||
public void touchLod(UUID worldId, int x, int z, @Nullable String reason)
|
||||
{
|
||||
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.debug("Touched LOD at " + this.getWorldInterface(worldId).getName() + " " + x + " " + z + (reason == null ? "." : ": " + reason));
|
||||
|
||||
this.touchedLods.put(key, lodModel);
|
||||
}
|
||||
|
||||
public void touchLod(UUID worldId, int x, int z)
|
||||
{
|
||||
this.touchLod(worldId, x, z, null);
|
||||
}
|
||||
|
||||
public void updateTouchedLods()
|
||||
{
|
||||
if (this.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : this.touchedLods.keySet()) {
|
||||
LodModel lodModelToDelete = this.touchedLods.get(key);
|
||||
this.touchedLods.remove(key);
|
||||
|
||||
WorldInterface world = this.getWorldInterface(lodModelToDelete.getWorldId());
|
||||
|
||||
this.debug("Changes detected in " + world.getName() + " " + lodModelToDelete.getX() + " " + lodModelToDelete.getZ() + ".");
|
||||
|
||||
this.getLodRepository().lodExistsAsync(lodModelToDelete.getWorldId(), lodModelToDelete.getX(), lodModelToDelete.getZ()).thenAccept((exists) -> {
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getLodRepository().deleteLodAsync(lodModelToDelete.getWorldId(), lodModelToDelete.getX(), lodModelToDelete.getZ())
|
||||
.thenAccept((deleted) -> {
|
||||
if (!deleted) {
|
||||
this.warning("Could not delete LOD " + world.getName() + " " + lodModelToDelete.getX() + " " + lodModelToDelete.getZ() + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
SectionPosition position = new SectionPosition();
|
||||
position.setDetailLevel(6);
|
||||
position.setX(lodModelToDelete.getX());
|
||||
position.setZ(lodModelToDelete.getZ());
|
||||
|
||||
this.getLod(lodModelToDelete.getWorldId(), position)
|
||||
.thenAcceptAsync((newLodModel) -> {
|
||||
Configuration worldConfig = world.getConfig();
|
||||
|
||||
// If this is false, then it will be false for all players as well.
|
||||
boolean updatesEnabled = worldConfig.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED);
|
||||
|
||||
if (!updatesEnabled) {
|
||||
this.debug("New LOD " + world.getName() + " " + lodModelToDelete.getX() + " " + lodModelToDelete.getZ() + " generated, but real-time updates are disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
String levelKeyPrefix = worldConfig.getString(DhsConfig.LEVEL_KEY_PREFIX);
|
||||
String levelKey = world.getKey();
|
||||
|
||||
if (levelKeyPrefix != null) {
|
||||
levelKey = levelKeyPrefix + levelKey;
|
||||
}
|
||||
|
||||
int lodChunkX = Coordinates.sectionToChunk(lodModelToDelete.getX());
|
||||
int lodChunkZ = Coordinates.sectionToChunk(lodModelToDelete.getZ());
|
||||
|
||||
int playersInRangeCount = 0;
|
||||
int playersOutOfRangeCount = 0;
|
||||
int playersWithoutDhCount = 0;
|
||||
|
||||
// TODO: Don't use Bukkit classes.
|
||||
for (Player player : Bukkit.getWorld(newLodModel.getWorldId()).getPlayers()) {
|
||||
Configuration playerConfig = this.getPlayerConfiguration(player.getUniqueId());
|
||||
|
||||
// No config for this player? Probably not using DH.
|
||||
if (playerConfig == null) {
|
||||
playersWithoutDhCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!playerConfig.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int updatesRadius = playerConfig.getInt(DhsConfig.REAL_TIME_UPDATE_RADIUS);
|
||||
|
||||
int playerChunkX = Coordinates.blockToChunk(player.getLocation().getBlockX());
|
||||
int playerChunkZ = Coordinates.blockToChunk(player.getLocation().getBlockZ());
|
||||
|
||||
int distanceX = Math.abs(Math.max(lodChunkX, playerChunkX) - Math.min(lodChunkX, playerChunkX));
|
||||
int distanceZ = Math.abs(Math.max(lodChunkZ, playerChunkZ) - Math.min(lodChunkZ, playerChunkZ));
|
||||
|
||||
// Update outside of player's range?
|
||||
if (distanceX > updatesRadius || distanceZ > updatesRadius) {
|
||||
playersOutOfRangeCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
playersInRangeCount++;
|
||||
|
||||
int myBufferId = playerConfig.increment("buffer-id");
|
||||
|
||||
FullDataPartialUpdateMessage partialUpdateMessage = new FullDataPartialUpdateMessage();
|
||||
partialUpdateMessage.setLevelKey(levelKey);
|
||||
partialUpdateMessage.setBufferId(myBufferId);
|
||||
partialUpdateMessage.setBeacons(newLodModel.getBeacons());
|
||||
|
||||
byte[] data = newLodModel.getData();
|
||||
|
||||
int chunkCount = (int) Math.ceil((double) data.length / LodHandler.CHUNK_SIZE);
|
||||
|
||||
for (int chunkNo = 0; chunkNo < chunkCount; chunkNo++) {
|
||||
FullDataChunkMessage chunkResponse = new FullDataChunkMessage();
|
||||
chunkResponse.setBufferId(myBufferId);
|
||||
chunkResponse.setIsFirst(chunkNo == 0);
|
||||
chunkResponse.setData(
|
||||
data,
|
||||
LodHandler.CHUNK_SIZE * chunkNo,
|
||||
Math.min(LodHandler.CHUNK_SIZE, data.length - LodHandler.CHUNK_SIZE * chunkNo)
|
||||
);
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(player.getUniqueId(), chunkResponse);
|
||||
}
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(player.getUniqueId(), partialUpdateMessage);
|
||||
}
|
||||
|
||||
this.debug("Updated LOD " + world.getName() + " " + lodModelToDelete.getX() + " " + lodModelToDelete.getZ() + " sent to " + playersInRangeCount + " players. Found " + playersOutOfRangeCount + " players out of range, and " + playersWithoutDhCount + " players without DH.");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPreGenerating(WorldInterface world)
|
||||
{
|
||||
return this.preGenerators.containsKey(world.getId()) && this.getPreGenerator(world).isRunning();
|
||||
}
|
||||
|
||||
public void preGenerate(WorldInterface world, int centerX, int centerZ, int radius, boolean force)
|
||||
{
|
||||
if (this.isPreGenerating(world)) {
|
||||
this.info("Cannot run multiple pre-generators in the same world. Stopping current task for " + world.getName() + "...");
|
||||
|
||||
this.stopPreGenerator(world);
|
||||
}
|
||||
|
||||
this.preGenerators.put(world.getId(), new PreGenerator(this, world, centerX, centerZ, radius, force));
|
||||
|
||||
this.getScheduler().runOnSeparateThread(() -> {
|
||||
this.preGenerators.get(world.getId()).run();
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public @Nullable PreGenerator getPreGenerator(WorldInterface world)
|
||||
{
|
||||
return this.preGenerators.get(world.getId());
|
||||
}
|
||||
|
||||
public void stopPreGenerator(WorldInterface world)
|
||||
{
|
||||
if (this.isPreGenerating(world)) {
|
||||
this.getPreGenerator(world).stop();
|
||||
|
||||
this.preGenerators.remove(world.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> trim(WorldInterface world, int centerX, int centerZ, int radius)
|
||||
{
|
||||
return this.getLodRepository().trimLodsAsync(
|
||||
world.getId(),
|
||||
Coordinates.blockToSection(centerX - radius),
|
||||
Coordinates.blockToSection(centerZ - radius),
|
||||
Coordinates.blockToSection(centerX + radius),
|
||||
Coordinates.blockToSection(centerZ + radius)
|
||||
);
|
||||
}
|
||||
|
||||
public CompletableFuture<Double> benchmark(UUID worldId, int iterations, int concurrency)
|
||||
{
|
||||
WorldInterface world = this.getWorldInterface(worldId);
|
||||
|
||||
List<SectionPosition> positions = new ArrayList<>(concurrency);
|
||||
|
||||
for (int i = 0; i < concurrency; i++) {
|
||||
SectionPosition position = new SectionPosition();
|
||||
position.setX(Coordinates.blockToSection(world.getSpawnX()) + i);
|
||||
position.setZ(Coordinates.blockToSection(world.getSpawnZ()));
|
||||
|
||||
positions.add(position);
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(
|
||||
positions.stream()
|
||||
.map((position) -> this.generateLod(world.getId(), position, false))
|
||||
.toArray(CompletableFuture[]::new)
|
||||
)
|
||||
.thenCompose((foo) -> {
|
||||
List<CompletableFuture<?>> chains = new ArrayList<>(positions.size());
|
||||
|
||||
long ts = System.currentTimeMillis();
|
||||
|
||||
for (SectionPosition position : positions) {
|
||||
CompletableFuture<?> chain = CompletableFuture.completedFuture(null);
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
chain = chain.thenCompose((bar) -> this.generateLod(world.getId(), position, false));
|
||||
}
|
||||
|
||||
chains.add(chain);
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(chains.toArray(CompletableFuture[]::new))
|
||||
.thenApply((bar) -> {
|
||||
double elapsed = (System.currentTimeMillis() - ts) / 1000d;
|
||||
|
||||
return iterations * concurrency / elapsed;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PerformanceTracker
|
||||
{
|
||||
private static final int WINDOW_SIZE_MILLIS = 60 * 3 * 1000;
|
||||
|
||||
private final List<Long> pings = new ArrayList<>();
|
||||
|
||||
public synchronized void ping()
|
||||
{
|
||||
this.pings.add(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public synchronized void prune()
|
||||
{
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
this.pings.removeIf((pingTimeMillis) -> currentTimeMillis - pingTimeMillis > WINDOW_SIZE_MILLIS);
|
||||
}
|
||||
|
||||
public double getPingsPerSecond()
|
||||
{
|
||||
if (this.pings.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use the earliest ping to calculate window size if we don't have data for the full period.
|
||||
long windowSize = Math.min(
|
||||
WINDOW_SIZE_MILLIS / 1000,
|
||||
System.currentTimeMillis() - this.pings.get(0)
|
||||
);
|
||||
|
||||
this.prune();
|
||||
|
||||
return (double) this.pings.size() / windowSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.database.models.LodModel;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class PreGenerator implements Runnable
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected WorldInterface world;
|
||||
|
||||
protected int radius;
|
||||
|
||||
protected int centerX;
|
||||
|
||||
protected int centerZ;
|
||||
|
||||
protected boolean force;
|
||||
|
||||
protected int totalSteps;
|
||||
|
||||
protected int stepsSoFar = 0;
|
||||
|
||||
protected int inFlight = 0;
|
||||
|
||||
protected boolean run = true;
|
||||
|
||||
protected long startTime = 0;
|
||||
|
||||
protected long stopTime = 0;
|
||||
|
||||
public PreGenerator(DhSupport dhSupport, WorldInterface world, int centerX, int centerZ, int radius, boolean force)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.world = world;
|
||||
this.centerX = Coordinates.blockToSection(centerX);
|
||||
this.centerZ = Coordinates.blockToSection(centerZ);
|
||||
this.radius = radius;
|
||||
this.force = force;
|
||||
|
||||
this.totalSteps = (int) Math.pow((double) Coordinates.blockToChunk(this.radius) / 2, 2);
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
int rateLimit = this.world.getConfig().getInt(DhsConfig.FULL_DATA_REQUEST_CONCURRENCY_LIMIT);
|
||||
|
||||
int currentX = this.centerX;
|
||||
int currentZ = this.centerZ;
|
||||
|
||||
int[][] directions = {{ 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }};
|
||||
int dirIndex = 0;
|
||||
|
||||
List<CompletableFuture<LodModel>> requests = new ArrayList<>(rateLimit);
|
||||
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
OUTER: for (int step = 1; this.stepsSoFar < totalSteps; step++) {
|
||||
for (int directionChanges = 0; directionChanges < 2; directionChanges++) {
|
||||
for (int stepsOnThisSide = 0; stepsOnThisSide < step; stepsOnThisSide++) {
|
||||
if (!this.run) {
|
||||
break OUTER;
|
||||
}
|
||||
|
||||
this.stepsSoFar++;
|
||||
|
||||
if (this.stepsSoFar > this.totalSteps) {
|
||||
break OUTER;
|
||||
}
|
||||
|
||||
currentX += directions[dirIndex][0];
|
||||
currentZ += directions[dirIndex][1];
|
||||
|
||||
if (!this.force && this.dhSupport.lodRepository.lodExists(this.world.getId(), currentX, currentZ)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionPosition position = new SectionPosition();
|
||||
position.setX(currentX);
|
||||
position.setZ(currentZ);
|
||||
position.setDetailLevel(6);
|
||||
|
||||
CompletableFuture<LodModel> request = this.dhSupport.generateLod(this.world.getId(), position, true);
|
||||
|
||||
request.handle((lodModel, exception) -> this.inFlight--);
|
||||
|
||||
requests.add(request);
|
||||
|
||||
this.inFlight++;
|
||||
|
||||
while (this.inFlight >= rateLimit) {
|
||||
CompletableFuture.anyOf(requests.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
requests.removeIf(CompletableFuture::isDone);
|
||||
}
|
||||
}
|
||||
|
||||
dirIndex = (dirIndex + 1) % 4; // Change direction
|
||||
}
|
||||
}
|
||||
|
||||
this.stopTime = System.currentTimeMillis();
|
||||
|
||||
this.run = false;
|
||||
}
|
||||
|
||||
public int getCompletedRequests()
|
||||
{
|
||||
return this.stepsSoFar - this.inFlight;
|
||||
}
|
||||
|
||||
public int getTargetRequests()
|
||||
{
|
||||
return this.totalSteps;
|
||||
}
|
||||
|
||||
public float getProgress()
|
||||
{
|
||||
return (float) this.getCompletedRequests() / this.getTargetRequests();
|
||||
}
|
||||
|
||||
public Duration getElapsedTime()
|
||||
{
|
||||
if (this.startTime == 0) {
|
||||
return Duration.ZERO;
|
||||
}
|
||||
|
||||
return Duration.ofMillis((this.stopTime == 0 ? System.currentTimeMillis() : this.stopTime) - this.startTime);
|
||||
}
|
||||
|
||||
public boolean isRunning()
|
||||
{
|
||||
return this.run;
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
this.run = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
public class UpdateChecker
|
||||
{
|
||||
protected int projectId;
|
||||
|
||||
public UpdateChecker(int projectId)
|
||||
{
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
protected JSONObject getLatestRelease() throws IOException
|
||||
{
|
||||
URL url = URI.create("https://gitlab.com/api/v4/projects/" + this.projectId + "/releases").toURL();
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
|
||||
while ((line = in.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
||||
JSONArray releases = new JSONArray(response.toString());
|
||||
|
||||
if (releases.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (JSONObject) releases.get(0);
|
||||
}
|
||||
|
||||
protected String getLatestVersion()
|
||||
{
|
||||
try {
|
||||
return this.getLatestRelease().getString("tag_name");
|
||||
} catch (IOException exception) {
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isLatestVersion(String version)
|
||||
{
|
||||
String latestVersionString = this.getLatestVersion();
|
||||
|
||||
// If the version check fails, pretend like we're up to date.
|
||||
if (latestVersionString == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] myVersion = version.split("[.-]");
|
||||
String[] latestVersion = latestVersionString.split("\\.");
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int mySegment = Integer.parseInt(myVersion[i]);
|
||||
int latestSegment = Integer.parseInt(latestVersion[i]);
|
||||
|
||||
// Our version is newer than the latest release.
|
||||
if (mySegment > latestSegment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Our version is older than the latest release.
|
||||
if (mySegment < latestSegment) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Our version is equal to the latest release.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -19,19 +19,130 @@
|
||||
package no.jckf.dhsupport.core;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private static final Map<String, String> NAMESPACE_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, String> UNNAMESPACE_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static String bytesToBin(byte[] bytes)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (byte b : bytes) {
|
||||
builder.append(Integer.toBinaryString((b & 0xFF) | 0x0100).substring(1));
|
||||
}
|
||||
|
||||
return builder.toString().replaceAll("(.{4})", "$1 ").trim();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public static String humanReadableDuration(final Duration duration)
|
||||
{
|
||||
if (duration.toDays() >= 365) {
|
||||
return "infinite";
|
||||
}
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (duration.toDays() > 0) {
|
||||
builder.append(duration.toDaysPart()).append(" day");
|
||||
|
||||
if (duration.toDaysPart() != 1) {
|
||||
builder.append("s");
|
||||
}
|
||||
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
if (duration.toHours() > 0) {
|
||||
builder.append(duration.toHoursPart()).append(" hour");
|
||||
|
||||
if (duration.toHoursPart() != 1) {
|
||||
builder.append("s");
|
||||
}
|
||||
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
if (duration.toMinutes() > 0) {
|
||||
builder.append(duration.toMinutesPart()).append(" minute");
|
||||
|
||||
if (duration.toMinutesPart() != 1) {
|
||||
builder.append("s");
|
||||
}
|
||||
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
if (duration.toSeconds() > 0 || builder.isEmpty()) {
|
||||
builder.append(duration.toSecondsPart()).append(" second");
|
||||
|
||||
if (duration.toSecondsPart() != 1) {
|
||||
builder.append("s");
|
||||
}
|
||||
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
return builder.substring(0, builder.length() - 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For awareness: This method caches results by key only.
|
||||
*/
|
||||
public static String namespaceKey(String key, String defaultNamespace)
|
||||
{
|
||||
return NAMESPACE_CACHE.computeIfAbsent(key, (k) -> k.contains(":") ? k : defaultNamespace + ":" + k);
|
||||
}
|
||||
|
||||
public static String namespaceKey(String key)
|
||||
{
|
||||
return namespaceKey(key, "minecraft");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated For awareness: This method caches results by key only.
|
||||
*/
|
||||
public static String unNamespaceKey(String key, String namespace)
|
||||
{
|
||||
return UNNAMESPACE_CACHE.computeIfAbsent(key, (k) -> k.startsWith(namespace + ":") ? k.substring(namespace.length() + 1) : k);
|
||||
}
|
||||
|
||||
public static String unNamespaceKey(String key)
|
||||
{
|
||||
return unNamespaceKey(key, "minecraft");
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class Decoder
|
||||
{
|
||||
int size = this.readInt();
|
||||
|
||||
List<T> items = new ArrayList<>();
|
||||
List<T> items = new ArrayList<>(Math.min(size, 128));
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
T item = this.readObject(className);
|
||||
|
||||
@@ -33,11 +33,21 @@ public class Encoder
|
||||
this.output = ByteStreams.newDataOutput();
|
||||
}
|
||||
|
||||
public Encoder(int size)
|
||||
{
|
||||
this.output = ByteStreams.newDataOutput(size);
|
||||
}
|
||||
|
||||
public void write(byte[] data)
|
||||
{
|
||||
this.output.write(data);
|
||||
}
|
||||
|
||||
public void write(byte[] data, int offset, int length)
|
||||
{
|
||||
this.output.write(data, offset, length);
|
||||
}
|
||||
|
||||
public void writeByte(byte value)
|
||||
{
|
||||
this.output.writeByte(value);
|
||||
|
||||
@@ -18,35 +18,105 @@
|
||||
|
||||
package no.jckf.dhsupport.core.configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
protected Map<String, Object> variables = new HashMap<>();
|
||||
protected Map<String, Object> variables = new ConcurrentHashMap<>();
|
||||
|
||||
public void set(String key, Object value)
|
||||
public void clear()
|
||||
{
|
||||
this.variables.clear();
|
||||
}
|
||||
|
||||
public boolean has(String key)
|
||||
{
|
||||
return this.variables.containsKey(key);
|
||||
}
|
||||
|
||||
public void set(String key, @Nullable Object value)
|
||||
{
|
||||
if (value instanceof String stringValue) {
|
||||
if ("false".equalsIgnoreCase(stringValue)) {
|
||||
value = false;
|
||||
} else if ("true".equalsIgnoreCase(stringValue)) {
|
||||
value = true;
|
||||
} else {
|
||||
try {
|
||||
value = Integer.parseInt(stringValue);
|
||||
} catch (NumberFormatException exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
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 @PolyNull Object get(String key, @PolyNull 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 @PolyNull Boolean getBool(String key, @PolyNull 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 @PolyNull Integer getInt(String key, @PolyNull Integer defaultValue)
|
||||
{
|
||||
return (Integer) this.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public @Nullable Integer getInt(String key)
|
||||
{
|
||||
return this.getInt(key, null);
|
||||
}
|
||||
|
||||
public @PolyNull String getString(String key, @PolyNull String defaultValue)
|
||||
{
|
||||
Object raw = this.get(key, defaultValue);
|
||||
|
||||
return raw == null ? null : String.valueOf(raw);
|
||||
}
|
||||
|
||||
public @Nullable String getString(String key)
|
||||
{
|
||||
return this.getString(key, null);
|
||||
}
|
||||
|
||||
public @PolyNull List<String> getStringList(String key, @PolyNull List<String> defaultValue)
|
||||
{
|
||||
return (List<String>) this.get(key, defaultValue);
|
||||
}
|
||||
|
||||
public List<String> getStringList(String key)
|
||||
{
|
||||
return this.getStringList(key, Collections.emptyList());
|
||||
}
|
||||
|
||||
public int increment(String key)
|
||||
{
|
||||
return (int) this.variables.compute(key, (k, v) -> v == null ? 1 : (int) v + 1);
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,15 @@
|
||||
|
||||
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 HOST = "host";
|
||||
public static String CONFIG_VERSION = "config_version";
|
||||
|
||||
public static String PORT = "port";
|
||||
public static String DEBUG = "debug";
|
||||
|
||||
public static String DATABASE_PATH = "database_path";
|
||||
|
||||
public static String CHECK_FOR_UPDATES = "check_for_updates";
|
||||
|
||||
public static String RENDER_DISTANCE = "render_distance";
|
||||
|
||||
@@ -36,30 +34,65 @@ public abstract class DhsConfig
|
||||
|
||||
public static String FULL_DATA_REQUEST_CONCURRENCY_LIMIT = "full_data_request_concurrency_limit";
|
||||
|
||||
public static String GENERATION_TASK_PRIORITY_REQUEST_RATE_LIMIT = "generation_task_priority_request_rate_limit";
|
||||
|
||||
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 String GENERATE_MULTIPLE_DIMENSIONS = "generate_multiple_dimensions";
|
||||
public static String MAX_DATA_TRANSFER_SPEED = "max_data_transfer_speed";
|
||||
|
||||
public static Collection<String> getKeys()
|
||||
{
|
||||
List<String> keys = new ArrayList<>();
|
||||
public static String SCHEDULER_THREADS = "scheduler_threads";
|
||||
|
||||
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 SERVER_KEY = "server_key";
|
||||
|
||||
return keys;
|
||||
}
|
||||
public static String LEVEL_KEY_PREFIX = "level_key_prefix";
|
||||
|
||||
public static String BORDER_CENTER_X = "border_center_x";
|
||||
|
||||
public static String BORDER_CENTER_Z = "border_center_z";
|
||||
|
||||
public static String BORDER_RADIUS = "border_radius";
|
||||
|
||||
public static String USE_VANILLA_WORLD_BORDER = "use_vanilla_world_border";
|
||||
|
||||
public static String VANILLA_WORLD_BORDER_EXPANSION = "vanilla_world_border_expansion";
|
||||
|
||||
public static String LOD_REFRESH_INTERVAL = "lod_refresh_interval";
|
||||
|
||||
public static String GENERATE_NEW_CHUNKS = "generate_new_chunks";
|
||||
|
||||
public static String GENERATE_NEW_CHUNKS_WARNING = "generate_new_chunks_warning";
|
||||
|
||||
public static String BUILDER_TYPE = "builder_type";
|
||||
|
||||
public static String TRUST_HEIGHT_MAP = "trust_height_map";
|
||||
|
||||
public static String BUILDER_RESOLUTION = "builder_resolution";
|
||||
|
||||
public static String SCAN_TO_SEA_LEVEL= "scan_to_sea_level";
|
||||
|
||||
public static String FAST_UNDERFILL = "fast_underfill";
|
||||
|
||||
public static String INCLUDE_NON_COLLIDING_TOP_LAYER = "include_non-colliding_top_layer";
|
||||
|
||||
public static String PERFORM_UNDERGLOW_HACK = "perform_underglow_hack";
|
||||
|
||||
public static String SAMPLE_BIOMES_3D = "sample_biomes_3d";
|
||||
|
||||
public static String UPDATE_EVENTS = "update_events";
|
||||
|
||||
public static String ENABLE_DH_COMMAND_HELP_TEXT = "enable_dh_command_help_text";
|
||||
|
||||
public static String DUMMY_CHUNK = "dummy_chunk";
|
||||
|
||||
public static String MATERIAL_MAP = "material_map";
|
||||
|
||||
public static String AD_MESSAGE = "ad_message";
|
||||
|
||||
public static String LIGHT_OFFSET_HACK = "light_offset_hack";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
|
||||
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 @PolyNull Object get(String key, @PolyNull 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,177 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Database
|
||||
{
|
||||
protected String path;
|
||||
|
||||
protected Connection connection;
|
||||
|
||||
protected Map<String, PreparedStatement> preparedStatements = new ConcurrentHashMap<>();
|
||||
|
||||
protected List<Class<? extends Migration>> migrations = new ArrayList<>();
|
||||
|
||||
protected CompletableFuture<?> optimizing;
|
||||
|
||||
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
|
||||
{
|
||||
this.clearQueryCache();
|
||||
|
||||
if (this.connection == null || this.connection.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getConnection().close();
|
||||
}
|
||||
|
||||
public PreparedStatement prepareAndReuse(String sql) throws SQLException
|
||||
{
|
||||
this.waitForOptimization();
|
||||
|
||||
if (!this.preparedStatements.containsKey(sql)) {
|
||||
this.preparedStatements.put(sql, this.getConnection().prepareStatement(sql));
|
||||
}
|
||||
|
||||
return this.preparedStatements.get(sql);
|
||||
}
|
||||
|
||||
public void clearQueryCache()
|
||||
{
|
||||
for (String key : this.preparedStatements.keySet()) {
|
||||
try {
|
||||
this.preparedStatements.remove(key).close();
|
||||
} catch (SQLException exception) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.add(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 (Class<? extends Migration> migrationClass : this.migrations) {
|
||||
String name = migrationClass.getSimpleName();
|
||||
|
||||
if (this.hasMigrationRan(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Migration migration = migrationClass.getConstructor(this.getClass()).newInstance(this);
|
||||
|
||||
migration.up();
|
||||
|
||||
this.markMigrationAsRan(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void optimize() throws Exception
|
||||
{
|
||||
this.optimizing = new CompletableFuture<>();
|
||||
|
||||
this.clearQueryCache();
|
||||
|
||||
try (Statement statement = this.getConnection().createStatement()) {
|
||||
statement.execute("PRAGMA optimize");
|
||||
statement.execute("ANALYZE");
|
||||
statement.execute("REINDEX");
|
||||
statement.execute("VACUUM");
|
||||
} finally {
|
||||
this.optimizing.complete(null);
|
||||
this.optimizing = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForOptimization()
|
||||
{
|
||||
if (this.optimizing != null) {
|
||||
this.optimizing.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-37
@@ -1,37 +1,41 @@
|
||||
/*
|
||||
* 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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class CloseSocketMessage extends SocketMessage
|
||||
{
|
||||
protected String message;
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeString(this.message);
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 AddLodVersionColumn extends Migration
|
||||
{
|
||||
public AddLodVersionColumn(Database database)
|
||||
{
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void up() throws Exception
|
||||
{
|
||||
try (Statement statement = this.database.getConnection().createStatement()) {
|
||||
statement.execute("""
|
||||
ALTER TABLE lods ADD version INT NULL;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 DeleteLodsWithoutVersion extends Migration
|
||||
{
|
||||
public DeleteLodsWithoutVersion(Database database)
|
||||
{
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void up() throws Exception
|
||||
{
|
||||
try (Statement statement = this.database.getConnection().createStatement()) {
|
||||
statement.execute("""
|
||||
DELETE FROM lods WHERE version IS NULL;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-6
@@ -16,15 +16,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.database.migrations;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.database.Database;
|
||||
|
||||
public class GenerationTaskPriorityResponse extends TrackableSocketMessage
|
||||
public abstract class Migration
|
||||
{
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
protected Database database;
|
||||
|
||||
public Migration(Database database)
|
||||
{
|
||||
// TODO
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public abstract void up() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 int VERSION = 2;
|
||||
|
||||
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;
|
||||
|
||||
protected int version;
|
||||
|
||||
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 this.x;
|
||||
}
|
||||
|
||||
public LodModel setZ(int z)
|
||||
{
|
||||
this.z = z;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getZ()
|
||||
{
|
||||
return this.z;
|
||||
}
|
||||
|
||||
public LodModel setData(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getData()
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public LodModel setBeacons(byte[] beacons)
|
||||
{
|
||||
this.beacons = beacons;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public byte[] getBeacons()
|
||||
{
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
public LodModel setTimestamp(int timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTimestamp()
|
||||
{
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public LodModel setVersion(int version)
|
||||
{
|
||||
this.version = version;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getVersion()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return this.getWorldId() + "@" + this.getX() + "x" + this.getZ();
|
||||
}
|
||||
}
|
||||
+24
-24
@@ -1,24 +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.message.plugin;
|
||||
|
||||
public class CurrentLevelKeyMessage extends PluginMessage
|
||||
{
|
||||
|
||||
}
|
||||
/*
|
||||
* 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,78 @@
|
||||
/*
|
||||
* 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, int version)
|
||||
{
|
||||
return this.queueTask(() -> this.saveLod(worldId, sectionX, sectionZ, data, beacons, version));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> trimLodsAsync(UUID worldId, int lowSectionX, int lowSectionZ, int highSectionX, int highSectionZ)
|
||||
{
|
||||
return this.queueTask(() -> this.trimLods(worldId, lowSectionX,lowSectionZ, highSectionX, highSectionZ));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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 static final String SQL_SAVE_LOD = "REPLACE INTO lods (worldId, x, z, data, beacons, timestamp, version) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
protected static final String SQL_LOAD_LOD = "SELECT data, beacons, timestamp FROM lods WHERE worldId = ? AND x = ? AND z = ? LIMIT 1";
|
||||
|
||||
protected static final String SQL_LOD_EXISTS = "SELECT EXISTS( SELECT 1 FROM lods WHERE worldId = ? AND x = ? AND z = ? )";
|
||||
|
||||
protected static final String SQL_DELETE_LOD = "DELETE FROM lods WHERE worldId = ? AND x = ? AND z = ?";
|
||||
|
||||
protected static final String SQL_TRIM_LODS = "DELETE FROM lods WHERE worldId = ? AND (x < ? OR z < ? OR x > ? OR z > ?)";
|
||||
|
||||
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 version)
|
||||
{
|
||||
int timestamp = (int) (System.currentTimeMillis() / 1000);
|
||||
|
||||
try {
|
||||
PreparedStatement statement = this.database.prepareAndReuse(SQL_SAVE_LOD);
|
||||
|
||||
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.setInt(7, version);
|
||||
|
||||
statement.executeUpdate();
|
||||
|
||||
return LodModel.create()
|
||||
.setWorldId(worldId)
|
||||
.setX(sectionX)
|
||||
.setZ(sectionZ)
|
||||
.setData(data)
|
||||
.setBeacons(beacons)
|
||||
.setTimestamp(timestamp)
|
||||
.setVersion(version);
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not save LOD: " + exception);
|
||||
|
||||
this.database.clearQueryCache();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public LodModel loadLod(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
try {
|
||||
PreparedStatement statement = this.database.prepareAndReuse(SQL_LOAD_LOD);
|
||||
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
result.next();
|
||||
|
||||
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);
|
||||
|
||||
this.database.clearQueryCache();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean lodExists(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
try {
|
||||
PreparedStatement statement = this.database.prepareAndReuse(SQL_LOD_EXISTS);
|
||||
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
result.next();
|
||||
|
||||
return result.getInt(1) == 1;
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not check LOD existence: " + exception);
|
||||
|
||||
this.database.clearQueryCache();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteLod(UUID worldId, int sectionX, int sectionZ)
|
||||
{
|
||||
try {
|
||||
PreparedStatement statement = this.database.prepareAndReuse(SQL_DELETE_LOD);
|
||||
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, sectionX);
|
||||
statement.setInt(3, sectionZ);
|
||||
|
||||
int affectedRows = statement.executeUpdate();
|
||||
|
||||
return affectedRows > 0;
|
||||
} catch (SQLException exception) {
|
||||
this.getLogger().warning("Could not delete LOD: " + exception);
|
||||
|
||||
this.database.clearQueryCache();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int trimLods(UUID worldId, int lowSectionX, int lowSectionZ, int highSectionX, int highSectionZ)
|
||||
{
|
||||
int affectedRows = 0;
|
||||
|
||||
try {
|
||||
PreparedStatement statement = this.database.prepareAndReuse(SQL_TRIM_LODS);
|
||||
|
||||
statement.setString(1, worldId.toString());
|
||||
statement.setInt(2, lowSectionX);
|
||||
statement.setInt(3, lowSectionZ);
|
||||
statement.setInt(4, highSectionX);
|
||||
statement.setInt(5, highSectionZ);
|
||||
|
||||
affectedRows = statement.executeUpdate();
|
||||
|
||||
this.database.optimize();
|
||||
} catch (Exception exception) {
|
||||
this.getLogger().warning("Could not trim LODs/optimize DB: " + exception);
|
||||
}
|
||||
|
||||
return affectedRows;
|
||||
}
|
||||
}
|
||||
+22
-15
@@ -16,30 +16,37 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.dataobject;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class ExceptionSocketMessage extends TrackableSocketMessage
|
||||
public class Beacon extends DataObject
|
||||
{
|
||||
protected int exceptionTypeId;
|
||||
protected BlockPosition position;
|
||||
|
||||
protected String exceptionMessage;
|
||||
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)
|
||||
{
|
||||
encoder.writeInt(this.exceptionTypeId);
|
||||
encoder.writeString(this.exceptionMessage);
|
||||
}
|
||||
this.position.encode(encoder);
|
||||
|
||||
public void setExceptionTypeId(int typeId)
|
||||
{
|
||||
this.exceptionTypeId = typeId;
|
||||
}
|
||||
|
||||
public void setExceptionMessage(String message)
|
||||
{
|
||||
this.exceptionMessage = message;
|
||||
encoder.writeInt(this.color);
|
||||
}
|
||||
}
|
||||
+80
-49
@@ -1,49 +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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class HelloSocketMessage extends SocketMessage
|
||||
{
|
||||
protected int version;
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.version = decoder.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.version);
|
||||
}
|
||||
|
||||
public int getVersion()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public void setVersion(int version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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,76 +18,102 @@
|
||||
|
||||
package no.jckf.dhsupport.core.dataobject;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class DataPoint extends DataObject
|
||||
{
|
||||
protected byte skyLight = 0;
|
||||
protected static int MAPPING_ID_OFFSET = 0;
|
||||
protected static int MAPPING_ID_BITS = 32;
|
||||
protected static long MAPPING_ID_MASK = 0xFFFFFFFFL;
|
||||
|
||||
protected byte blockLight = 0;
|
||||
protected static int HEIGHT_OFFSET = MAPPING_ID_OFFSET + MAPPING_ID_BITS;
|
||||
protected static int HEIGHT_BITS = 12;
|
||||
protected static long HEIGHT_MASK = 0x0FFF;
|
||||
|
||||
protected int height = 1;
|
||||
protected static int START_Y_OFFSET = HEIGHT_OFFSET + HEIGHT_BITS;
|
||||
protected static int START_Y_BITS = 12;
|
||||
protected static long START_Y_MASK = 0x0FFF;
|
||||
|
||||
protected int startY = 0;
|
||||
protected static int SKY_LIGHT_OFFSET = START_Y_OFFSET + START_Y_BITS;
|
||||
protected static int SKY_LIGHT_BITS = 4;
|
||||
protected static long SKY_LIGHT_MASK = 0x0F;
|
||||
|
||||
protected int mappingId;
|
||||
protected static int BLOCK_LIGHT_OFFSET = SKY_LIGHT_OFFSET + SKY_LIGHT_BITS;
|
||||
protected static int BLOCK_LIGHT_BITS = 4;
|
||||
protected static long BLOCK_LIGHT_MASK = 0x0F;
|
||||
|
||||
public void setSkyLight(byte value)
|
||||
protected long data = 0;
|
||||
|
||||
public void setMappingId(int mappingId)
|
||||
{
|
||||
this.skyLight = value;
|
||||
}
|
||||
this.data &= ~(MAPPING_ID_MASK << MAPPING_ID_OFFSET);
|
||||
|
||||
public byte getSkyLight()
|
||||
{
|
||||
return this.skyLight;
|
||||
}
|
||||
|
||||
public void setBlockLight(byte value)
|
||||
{
|
||||
this.blockLight = value;
|
||||
}
|
||||
|
||||
public void setHeight(int value)
|
||||
{
|
||||
this.height = value;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setStartY(int value)
|
||||
{
|
||||
this.startY = value;
|
||||
}
|
||||
|
||||
public int getStartY()
|
||||
{
|
||||
return this.startY;
|
||||
}
|
||||
|
||||
public void setMappingId(int id)
|
||||
{
|
||||
this.mappingId = id;
|
||||
this.data |= mappingId & MAPPING_ID_MASK;
|
||||
}
|
||||
|
||||
public int getMappingId()
|
||||
{
|
||||
return this.mappingId;
|
||||
return (int) ((this.data >> MAPPING_ID_OFFSET) & MAPPING_ID_MASK);
|
||||
}
|
||||
|
||||
public void setHeight(int height)
|
||||
{
|
||||
this.data &= ~(HEIGHT_MASK << HEIGHT_OFFSET);
|
||||
|
||||
this.data |= (height & HEIGHT_MASK) << HEIGHT_OFFSET;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return (int) ((this.data >> HEIGHT_OFFSET) & HEIGHT_MASK);
|
||||
}
|
||||
|
||||
public void setStartY(int startY)
|
||||
{
|
||||
this.data &= ~(START_Y_MASK << START_Y_OFFSET);
|
||||
|
||||
this.data |= ((long) startY & START_Y_MASK) << START_Y_OFFSET;
|
||||
}
|
||||
|
||||
public int getStartY()
|
||||
{
|
||||
return (int) ((this.data >> START_Y_OFFSET) & START_Y_MASK);
|
||||
}
|
||||
|
||||
public void setSkyLight(byte skyLight)
|
||||
{
|
||||
this.data &= ~(SKY_LIGHT_MASK << SKY_LIGHT_OFFSET);
|
||||
|
||||
this.data |= (skyLight & SKY_LIGHT_MASK) << SKY_LIGHT_OFFSET;
|
||||
}
|
||||
|
||||
public byte getSkyLight()
|
||||
{
|
||||
return (byte) ((this.data >> SKY_LIGHT_OFFSET) & SKY_LIGHT_MASK);
|
||||
}
|
||||
|
||||
public void setBlockLight(byte blockLight)
|
||||
{
|
||||
this.data &= ~(BLOCK_LIGHT_MASK << BLOCK_LIGHT_OFFSET);
|
||||
|
||||
this.data |= (blockLight & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_OFFSET;
|
||||
}
|
||||
|
||||
public byte getBlockLight()
|
||||
{
|
||||
return (byte) ((this.data >> BLOCK_LIGHT_OFFSET) & BLOCK_LIGHT_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
long data = 0;
|
||||
|
||||
data |= this.mappingId;
|
||||
data |= (long) (this.height & 0x0FFF) << 32;
|
||||
data |= (long) (this.startY & 0x0FFF) << 32 + 12;
|
||||
data |= (long) (this.skyLight & 0x0F) << 32 + 12 + 12;
|
||||
data |= (long) (this.blockLight & 0x0F) << 32 + 12 + 12 + 4;
|
||||
|
||||
encoder.writeLong(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.data = decoder.readLong();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package no.jckf.dhsupport.core.dataobject;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class IdMapping extends DataObject
|
||||
@@ -44,6 +43,22 @@ public class IdMapping extends DataObject
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getBiome()
|
||||
{
|
||||
return this.biome;
|
||||
}
|
||||
|
||||
public String getBlock()
|
||||
{
|
||||
return this.block;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, String> getProperties()
|
||||
{
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
@@ -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,10 +38,14 @@ 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;
|
||||
|
||||
protected static volatile byte[] compressedGenerationStep;
|
||||
|
||||
protected static volatile byte[] compressedWorldCompressionType;
|
||||
|
||||
protected WorldInterface worldInterface;
|
||||
|
||||
protected SectionPosition position;
|
||||
@@ -50,17 +54,20 @@ public class Lod extends DataObject
|
||||
|
||||
protected List<List<DataPoint>> columns;
|
||||
|
||||
public Lod(WorldInterface worldInterface, SectionPosition position, List<IdMapping> idMappings, List<List<DataPoint>> columns)
|
||||
protected List<Beacon> beacons;
|
||||
|
||||
public Lod(WorldInterface worldInterface, SectionPosition position, List<IdMapping> idMappings, List<List<DataPoint>> columns, List<Beacon> beacons)
|
||||
{
|
||||
this.worldInterface = worldInterface;
|
||||
this.position = position;
|
||||
this.idMappings = idMappings;
|
||||
this.columns = columns;
|
||||
this.beacons = beacons;
|
||||
}
|
||||
|
||||
protected void encodeData(Encoder encoder)
|
||||
{
|
||||
Encoder toCompress = new Encoder();
|
||||
Encoder toCompress = new Encoder(8192);
|
||||
|
||||
for (List<DataPoint> column : this.columns) {
|
||||
toCompress.writeShort(column.size());
|
||||
@@ -76,12 +83,25 @@ public class Lod extends DataObject
|
||||
encoder.write(compressed);
|
||||
}
|
||||
|
||||
protected void encodeAdjacentData(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(0);
|
||||
encoder.writeInt(0);
|
||||
encoder.writeInt(0);
|
||||
encoder.writeInt(0);
|
||||
}
|
||||
|
||||
protected void encodeColumnGenerationStep(Encoder encoder)
|
||||
{
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width];
|
||||
Arrays.fill(bytesToCompress, (byte) GenerationStep.STRUCTURE_START.value);
|
||||
byte[] compressed = Lod.compressedGenerationStep;
|
||||
|
||||
byte[] compressed = this.compress(bytesToCompress);
|
||||
if (compressed == null) {
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width];
|
||||
Arrays.fill(bytesToCompress, (byte) GenerationStep.LIGHT.value);
|
||||
|
||||
compressed = this.compress(bytesToCompress);
|
||||
Lod.compressedGenerationStep = compressed;
|
||||
}
|
||||
|
||||
encoder.writeInt(compressed.length);
|
||||
encoder.write(compressed);
|
||||
@@ -89,9 +109,14 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeWorldCompressionType(Encoder encoder)
|
||||
{
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width]; // All zeroes means WorldCompressionType.STRICT
|
||||
byte[] compressed = Lod.compressedWorldCompressionType;
|
||||
|
||||
byte[] compressed = this.compress(bytesToCompress);
|
||||
if (compressed == null) {
|
||||
byte[] bytesToCompress = new byte[Lod.width * Lod.width]; // All zeroes means WorldCompressionType.STRICT
|
||||
|
||||
compressed = this.compress(bytesToCompress);
|
||||
Lod.compressedWorldCompressionType = compressed;
|
||||
}
|
||||
|
||||
encoder.writeInt(compressed.length);
|
||||
encoder.write(compressed);
|
||||
@@ -99,7 +124,7 @@ public class Lod extends DataObject
|
||||
|
||||
protected void encodeMappings(Encoder encoder)
|
||||
{
|
||||
Encoder toCompress = new Encoder();
|
||||
Encoder toCompress = new Encoder(1024);
|
||||
|
||||
toCompress.writeInt(this.idMappings.size());
|
||||
|
||||
@@ -118,9 +143,12 @@ public class Lod extends DataObject
|
||||
{
|
||||
this.position.encode(encoder);
|
||||
|
||||
encoder.writeInt(0); // TODO: Checksum.
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
encoder.writeInt(0); // Checksum
|
||||
|
||||
this.encodeData(encoder);
|
||||
this.encodeAdjacentData(encoder);
|
||||
this.encodeColumnGenerationStep(encoder);
|
||||
this.encodeWorldCompressionType(encoder);
|
||||
this.encodeMappings(encoder);
|
||||
@@ -130,9 +158,10 @@ public class Lod extends DataObject
|
||||
encoder.writeByte(Lod.compressionType.value);
|
||||
|
||||
encoder.writeBoolean(true); // Apply to parent
|
||||
encoder.writeBoolean(false); // Apply to children
|
||||
|
||||
encoder.writeLong(0); // TODO: Last modified TS.
|
||||
encoder.writeLong(0); // TODO: Created TS.
|
||||
encoder.writeLong(now); // Created at
|
||||
encoder.writeLong(now); // Updated at
|
||||
}
|
||||
|
||||
protected byte[] compress(byte[] uncompressedData)
|
||||
@@ -140,14 +169,11 @@ 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
|
||||
);
|
||||
LZMA2Options options = new LZMA2Options(3);
|
||||
|
||||
options.setDictSize(Math.max(LZMA2Options.DICT_SIZE_MIN, Math.min(uncompressedData.length, 1 << 20)));
|
||||
|
||||
OutputStream compressorStream = new XZOutputStream(compressedStream, options, XZ.CHECK_CRC64);
|
||||
|
||||
compressorStream.write(uncompressedData);
|
||||
compressorStream.flush();
|
||||
@@ -158,4 +184,9 @@ public class Lod extends DataObject
|
||||
|
||||
return compressedStream.toByteArray();
|
||||
}
|
||||
|
||||
public List<Beacon> getBeacons()
|
||||
{
|
||||
return this.beacons;
|
||||
}
|
||||
}
|
||||
@@ -23,55 +23,77 @@ import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class SectionPosition extends DataObject
|
||||
{
|
||||
protected int detailLevel;
|
||||
public static final int DETAIL_LEVEL_OFFSET = 0;
|
||||
public static final int DETAIL_LEVEL_BITS = 8;
|
||||
public static final long DETAIL_LEVEL_MASK = 0xFF;
|
||||
|
||||
protected int x;
|
||||
public static final int X_OFFSET = DETAIL_LEVEL_OFFSET + DETAIL_LEVEL_BITS;
|
||||
public static final int X_BITS = 28;
|
||||
public static final long X_MASK = 0x0FFFFFFF;
|
||||
|
||||
protected int z;
|
||||
public static final int Z_OFFSET = X_OFFSET + X_BITS;
|
||||
public static final int Z_BITS = 28;
|
||||
public static final long Z_MASK = 0x0FFFFFFF;
|
||||
|
||||
public void setDetailLevel(int level)
|
||||
protected long data = 0;
|
||||
|
||||
public void setDetailLevel(int detailLevel)
|
||||
{
|
||||
this.detailLevel = level;
|
||||
this.data &= ~(DETAIL_LEVEL_MASK << DETAIL_LEVEL_OFFSET);
|
||||
|
||||
this.data |= (detailLevel & DETAIL_LEVEL_MASK) << DETAIL_LEVEL_OFFSET;
|
||||
}
|
||||
|
||||
public int getDetailLevel()
|
||||
{
|
||||
return this.detailLevel;
|
||||
return (int) (data & DETAIL_LEVEL_MASK);
|
||||
}
|
||||
|
||||
public void setX(int x)
|
||||
{
|
||||
this.x = x;
|
||||
this.data &= ~(X_MASK << X_OFFSET);
|
||||
|
||||
this.data |= (x & X_MASK) << X_OFFSET;
|
||||
}
|
||||
|
||||
public int getX()
|
||||
{
|
||||
return this.x;
|
||||
int raw = (int) ((data >> X_OFFSET) & X_MASK);
|
||||
|
||||
if ((raw & (1 << 27)) != 0) {
|
||||
raw |= (int) ~X_MASK;
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
public void setZ(int z)
|
||||
{
|
||||
this.z = z;
|
||||
this.data &= ~(Z_MASK << Z_OFFSET);
|
||||
|
||||
this.data |= (z & Z_MASK) << Z_OFFSET;
|
||||
}
|
||||
|
||||
public int getZ()
|
||||
{
|
||||
return this.z;
|
||||
int raw = (int) ((data >> Z_OFFSET) & Z_MASK);
|
||||
|
||||
if ((raw & (1 << 27)) != 0) {
|
||||
raw |= (int) ~Z_MASK;
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeByte(this.detailLevel);
|
||||
encoder.writeInt(this.x);
|
||||
encoder.writeInt(this.z);
|
||||
encoder.writeLong(this.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.detailLevel = decoder.readByte();
|
||||
this.x = decoder.readInt();
|
||||
this.z = decoder.readInt();
|
||||
this.data = decoder.readLong();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.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.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.UUID;
|
||||
|
||||
public class LodHandler
|
||||
{
|
||||
public static int CHUNK_SIZE = 1024 * 16; // TODO: Configurable?
|
||||
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected PluginMessageHandler pluginMessageHandler;
|
||||
|
||||
public LodHandler(DhSupport dhSupport, PluginMessageHandler pluginMessageHandler)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.pluginMessageHandler = pluginMessageHandler;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(FullDataSourceRequestMessage.class, (requestMessage) -> {
|
||||
//this.dhSupport.info("LOD request for " + requestMessage.getPosition().getX() + " " + requestMessage.getPosition().getZ());
|
||||
|
||||
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();
|
||||
|
||||
WorldInterface world = this.dhSupport.getWorldInterface(worldUuid);
|
||||
|
||||
Configuration config = world.getConfig();
|
||||
Configuration playerConfig = this.dhSupport.getPlayerConfiguration(requestMessage.getSender());
|
||||
|
||||
if (!config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED) || !playerConfig.getBool(DhsConfig.DISTANT_GENERATION_ENABLED)) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_SECTION_REQUIRES_SPLITTING);
|
||||
exceptionMessage.setMessage("Server has disabled distant generation");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
SectionPosition position = requestMessage.getPosition();
|
||||
|
||||
String builderType = config.getString(DhsConfig.BUILDER_TYPE);
|
||||
|
||||
if (builderType.equalsIgnoreCase("none") && !this.dhSupport.getLodRepository().lodExists(worldUuid, position.getX(), position.getZ())) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("Server has disabled LOD builder");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dhSupport.isPaused() && !this.dhSupport.getLodRepository().lodExists(worldUuid, position.getX(), position.getZ())) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("Server has paused Distant Horizons");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
int worldX = Coordinates.sectionToBlock(position.getX());
|
||||
int worldZ = Coordinates.sectionToBlock(position.getZ());
|
||||
|
||||
Integer borderCenterX = world.getWorldBorderX();
|
||||
Integer borderCenterZ = world.getWorldBorderZ();
|
||||
Integer borderRadius = world.getWorldBorderRadius();
|
||||
|
||||
if (borderCenterX != null && borderCenterZ != null && borderRadius != null) {
|
||||
int minX = borderCenterX - borderRadius;
|
||||
int maxX = borderCenterX + borderRadius;
|
||||
|
||||
int minZ = borderCenterZ - borderRadius;
|
||||
int maxZ = borderCenterZ + 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) -> {
|
||||
if (lodModel == null) {
|
||||
ExceptionMessage exceptionMessage = new ExceptionMessage();
|
||||
exceptionMessage.isResponseTo(requestMessage);
|
||||
exceptionMessage.setTypeId(ExceptionMessage.TYPE_REQUEST_REJECTED);
|
||||
exceptionMessage.setMessage("No LOD available");
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), exceptionMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
FullDataSourceResponseMessage responseMessage = new FullDataSourceResponseMessage();
|
||||
responseMessage.isResponseTo(requestMessage);
|
||||
|
||||
boolean sendData = requestMessage.getTimestamp() == null || (requestMessage.getTimestamp() / 1000) < lodModel.getTimestamp();
|
||||
|
||||
if (sendData) {
|
||||
int myBufferId = playerConfig.increment("buffer-id");
|
||||
|
||||
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(
|
||||
data,
|
||||
CHUNK_SIZE * chunkNo,
|
||||
Math.min(CHUNK_SIZE, data.length - CHUNK_SIZE * chunkNo)
|
||||
);
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), chunkResponse);
|
||||
}
|
||||
|
||||
//this.dhSupport.info("LOD in " + chunkCount + " parts sent for " + requestMessage.getPosition().getX() + " " + requestMessage.getPosition().getZ());
|
||||
}
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(requestMessage.getSender(), responseMessage);
|
||||
})
|
||||
.exceptionally((exception) -> {
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.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.LevelInitMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.LevelInitRequestMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.RemotePlayerConfigMessage;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerConfigHandler
|
||||
{
|
||||
public final static String USAGE_PERMISSION = "distant_horizons.worlds.%s";
|
||||
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected PluginMessageHandler pluginMessageHandler;
|
||||
|
||||
public PlayerConfigHandler(DhSupport dhSupport, PluginMessageHandler pluginMessageHandler)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.pluginMessageHandler = pluginMessageHandler;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(LevelInitRequestMessage.class, (initRequestMessage) -> {
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
Player player = Bukkit.getPlayer(initRequestMessage.getSender());
|
||||
|
||||
NamespacedKey worldKey = NamespacedKey.fromString(initRequestMessage.getWorldKey());
|
||||
|
||||
// TODO: This too 👎
|
||||
UUID worldUuid = null;
|
||||
|
||||
for (World world : Bukkit.getServer().getWorlds()) {
|
||||
if (world.getKey().equals(worldKey)) {
|
||||
worldUuid = world.getUID();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (worldUuid == null) {
|
||||
this.dhSupport.warning("Player " + player.getName() + " requested init for unknown world " + worldKey + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
WorldInterface world = this.dhSupport.getWorldInterface(worldUuid);
|
||||
|
||||
Configuration dhsConfig = world.getConfig();
|
||||
|
||||
String levelKeyPrefix = dhsConfig.getString(DhsConfig.LEVEL_KEY_PREFIX);
|
||||
String levelKey = world.getKey();
|
||||
|
||||
if (levelKeyPrefix != null) {
|
||||
levelKey = levelKeyPrefix + levelKey;
|
||||
}
|
||||
|
||||
LevelInitMessage levelInitResponse = new LevelInitMessage();
|
||||
levelInitResponse.setNamespacedKey(worldKey.toString());
|
||||
levelInitResponse.setServerKey(dhsConfig.getString(DhsConfig.SERVER_KEY, ""));
|
||||
levelInitResponse.setLevelKey(levelKey);
|
||||
levelInitResponse.setTime(System.currentTimeMillis());
|
||||
this.pluginMessageHandler.sendPluginMessage(initRequestMessage.getSender(), levelInitResponse);
|
||||
});
|
||||
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(RemotePlayerConfigMessage.class, (configMessage) -> {
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
Player player = Bukkit.getPlayer(configMessage.getSender());
|
||||
|
||||
WorldInterface world = this.dhSupport.getWorldInterface(player.getWorld().getUID());
|
||||
|
||||
String permission = USAGE_PERMISSION.formatted(world.getName());
|
||||
boolean dhUseIsAllowed = !player.isPermissionSet(permission) || player.hasPermission(permission);
|
||||
|
||||
Configuration dhsConfig = world.getConfig();
|
||||
|
||||
Configuration clientConfig = configMessage.toConfiguration();
|
||||
|
||||
// This is not very flexible, but will do for now.
|
||||
for (String key : RemotePlayerConfigMessage.KEYS) {
|
||||
//this.dhSupport.getLogger().info("Config key " + key + ":");
|
||||
|
||||
Object dhsValue = dhsConfig.get(key);
|
||||
Object clientValue = clientConfig.get(key);
|
||||
Object keepValue = null;
|
||||
|
||||
if (key.equals(DhsConfig.DISTANT_GENERATION_ENABLED)) {
|
||||
if (dhUseIsAllowed) {
|
||||
if (!(boolean) clientValue) {
|
||||
player.sendMessage(ChatColor.GOLD + "This server supports Distant Horizons, but you have disabled Distant Generation in your DH settings.");
|
||||
}
|
||||
} else {
|
||||
this.dhSupport.info("Player " + player.getName() + " has been denied the use of Distant Horizons through permissions.");
|
||||
|
||||
keepValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals(DhsConfig.REAL_TIME_UPDATES_ENABLED) && !dhUseIsAllowed) {
|
||||
keepValue = false;
|
||||
}
|
||||
|
||||
// TODO: This is ugly. Move to comparator closures like in DH.
|
||||
if (key.equals(DhsConfig.BORDER_CENTER_X)) {
|
||||
keepValue = world.getWorldBorderX();
|
||||
} else if (key.equals(DhsConfig.BORDER_CENTER_Z)) {
|
||||
keepValue = world.getWorldBorderZ();
|
||||
} else if(key.equals(DhsConfig.BORDER_RADIUS)) {
|
||||
keepValue = world.getWorldBorderRadius();
|
||||
}
|
||||
|
||||
if (keepValue == null) {
|
||||
if (dhsValue instanceof Boolean dhsBool && clientValue instanceof Boolean clientBool) {
|
||||
keepValue = dhsBool && clientBool;
|
||||
|
||||
//this.dhSupport.getLogger().info(" Server " + (dhsBool ? "Y" : "N") + " or client " + (clientBool ? "Y" : "N") + " = " + ((boolean) keepValue ? "Y" : "N"));
|
||||
} else if (dhsValue instanceof Integer dhsInt && clientValue instanceof Integer clientInt) {
|
||||
keepValue = dhsInt < clientInt ? dhsInt : clientInt;
|
||||
|
||||
//this.dhSupport.getLogger().info(" Server " + dhsInt + " or client " + clientInt + " = " + keepValue);
|
||||
} else {
|
||||
//this.dhSupport.getLogger().info(" Uhh... 😵💫");
|
||||
}
|
||||
}
|
||||
|
||||
clientConfig.set(key, keepValue == null ? -1 : keepValue);
|
||||
}
|
||||
|
||||
this.dhSupport.setPlayerConfiguration(configMessage.getSender(), clientConfig);
|
||||
|
||||
RemotePlayerConfigMessage configResponse = new RemotePlayerConfigMessage();
|
||||
configResponse.fromConfiguration(clientConfig);
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(configMessage.getSender(), configResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,7 @@ import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.event.EventBus;
|
||||
import no.jckf.dhsupport.core.message.MessageTypeRegistry;
|
||||
import no.jckf.dhsupport.core.message.plugin.CurrentLevelKeyMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.HelloPluginMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.PluginMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.ServerConnectInfoMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -39,9 +36,9 @@ public class PluginMessageHandler
|
||||
|
||||
private MessageTypeRegistry messageTypeRegistry;
|
||||
|
||||
public final String pluginChannel = "distant_horizons:plugin_channel";
|
||||
public final String pluginChannel = "distant_horizons:msg";
|
||||
|
||||
public final int protocolVersion = 1;
|
||||
public final short protocolVersion = 15;
|
||||
|
||||
private EventBus<PluginMessage> eventBus;
|
||||
|
||||
@@ -54,9 +51,16 @@ public class PluginMessageHandler
|
||||
// Define plugin channel message types.
|
||||
this.messageTypeRegistry = new MessageTypeRegistry();
|
||||
this.messageTypeRegistry.registerMessageType(0, null);
|
||||
this.messageTypeRegistry.registerMessageType(1, HelloPluginMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(2, CurrentLevelKeyMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(3, ServerConnectInfoMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(1, CloseReasonMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(2, LevelInitMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(3, LevelInitRequestMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(4, RemotePlayerConfigMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(5, CancelMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(6, ExceptionMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(7, FullDataSourceRequestMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(8, FullDataSourceResponseMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(9, FullDataPartialUpdateMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(10, FullDataChunkMessage.class);
|
||||
}
|
||||
|
||||
public void onEnable()
|
||||
@@ -80,7 +84,7 @@ public class PluginMessageHandler
|
||||
PluginMessage message;
|
||||
|
||||
try {
|
||||
message = this.readPluginMessage(data);
|
||||
message = this.readPluginMessage(senderUuid, data);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Error while parsing incoming plugin message: " + exception.getClass() + " - " + exception.getMessage());
|
||||
this.dhSupport.warning("Data was: " + Utils.bytesToHex(data));
|
||||
@@ -91,26 +95,29 @@ public class PluginMessageHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if (message instanceof CloseReasonMessage) {
|
||||
throw new RuntimeException(((CloseReasonMessage) message).getReason());
|
||||
}
|
||||
|
||||
message.setSender(senderUuid);
|
||||
|
||||
this.eventBus.dispatch(message);
|
||||
}
|
||||
|
||||
protected PluginMessage readPluginMessage(byte[] data)
|
||||
protected PluginMessage readPluginMessage(UUID senderUuid, byte[] data)
|
||||
{
|
||||
//this.dhSupport.info("Plugin message received. Length: " + data.length);
|
||||
|
||||
Decoder decoder = new Decoder(data);
|
||||
|
||||
// Read and discard sub-channel. DH always sends a null byte here.
|
||||
decoder.readByte();
|
||||
|
||||
// Read the client's protocol version.
|
||||
short protocolVersion = decoder.readShort();
|
||||
|
||||
if (protocolVersion != this.protocolVersion) {
|
||||
this.dhSupport.warning("Unsupported protocol version: " + protocolVersion);
|
||||
return null;
|
||||
CloseReasonMessage closeReasonMessage = new CloseReasonMessage();
|
||||
closeReasonMessage.setReason((protocolVersion > this.protocolVersion ? "Server" : "Client") + " is outdated.");
|
||||
this.sendPluginMessage(senderUuid, closeReasonMessage);
|
||||
return closeReasonMessage;
|
||||
}
|
||||
|
||||
// Read the message type ID.
|
||||
@@ -124,6 +131,11 @@ public class PluginMessageHandler
|
||||
|
||||
try {
|
||||
message = messageClass.getConstructor().newInstance();
|
||||
|
||||
if (message instanceof TrackablePluginMessage) {
|
||||
((TrackablePluginMessage) message).setTracker(decoder.readInt());
|
||||
}
|
||||
|
||||
message.decode(decoder);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to init message class: " + exception.getClass() + " - " + exception.getMessage());
|
||||
@@ -142,24 +154,22 @@ public class PluginMessageHandler
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
Encoder encoder = new Encoder(message.bufferSize());
|
||||
|
||||
try {
|
||||
Encoder encoder = new Encoder();
|
||||
message.encode(encoder);
|
||||
data = encoder.toByteArray();
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass() + ": " + exception.getClass() + " - " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
encoder.writeByte(0);
|
||||
encoder.writeShort(this.protocolVersion);
|
||||
encoder.writeShort(messageTypeId);
|
||||
|
||||
encoder.write(data);
|
||||
if (message instanceof TrackablePluginMessage) {
|
||||
encoder.writeInt(((TrackablePluginMessage) message).getTracker());
|
||||
}
|
||||
|
||||
try {
|
||||
message.encode(encoder);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass().getSimpleName());
|
||||
exception.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] fullMessage = encoder.toByteArray();
|
||||
|
||||
|
||||
@@ -1,209 +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.handler;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
import no.jckf.dhsupport.core.event.EventBus;
|
||||
import no.jckf.dhsupport.core.message.MessageTypeRegistry;
|
||||
import no.jckf.dhsupport.core.message.socket.*;
|
||||
import no.jckf.dhsupport.core.socketserver.SocketServer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class SocketMessageHandler
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected MessageTypeRegistry messageTypeRegistry;
|
||||
|
||||
protected SocketServer socketServer;
|
||||
|
||||
public final int protocolVersion = 3;
|
||||
|
||||
protected EventBus<SocketMessage> eventBus;
|
||||
|
||||
public SocketMessageHandler(DhSupport dhSupport)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
|
||||
this.eventBus = new EventBus<>();
|
||||
|
||||
this.socketServer = new SocketServer(this.dhSupport);
|
||||
|
||||
this.messageTypeRegistry = new MessageTypeRegistry();
|
||||
this.messageTypeRegistry.registerMessageType(0x0000, null);
|
||||
this.messageTypeRegistry.registerMessageType(0x0001, HelloSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0002, CloseSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0003, AckSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0004, CancelSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0005, ExceptionSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0006, PlayerUuidSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0007, PlayerConfigSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0008, FullDataRequestSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x0009, FullDataResponseSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x000a, PartialUpdateSocketMessage.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x000b, GenerationTaskPriorityRequest.class);
|
||||
this.messageTypeRegistry.registerMessageType(0x000c, GenerationTaskPriorityResponse.class);
|
||||
}
|
||||
|
||||
public void onEnable()
|
||||
{
|
||||
try {
|
||||
this.socketServer.startup(this.dhSupport.getConfig().getInt(DhsConfig.PORT));
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to start socket server: " + exception.getClass().getSimpleName() + " - " + exception.getMessage());
|
||||
// TODO: Do something about it?
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable()
|
||||
{
|
||||
try {
|
||||
this.socketServer.shutdown();
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to gracefully stop socket server: " + exception.getClass().getSimpleName() + " - " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EventBus<SocketMessage> getEventBus()
|
||||
{
|
||||
return this.eventBus;
|
||||
}
|
||||
|
||||
public void onSocketMessageReceived(Channel socket, byte[] data)
|
||||
{
|
||||
//this.dhSupport.info("Socket message received. Length: " + data.length);
|
||||
|
||||
SocketMessage message;
|
||||
|
||||
try {
|
||||
message = this.readSocketMessage(data);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Error while parsing incoming socket message: " + exception.getClass() + " - " + exception.getMessage());
|
||||
this.dhSupport.warning("Data was: " + Utils.bytesToHex(data));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
message.setSender(socket);
|
||||
|
||||
this.eventBus.dispatch(message);
|
||||
}
|
||||
|
||||
protected SocketMessage readSocketMessage(byte[] data)
|
||||
{
|
||||
Decoder decoder = new Decoder(data);
|
||||
|
||||
short messageTypeId = decoder.readShort();
|
||||
|
||||
Class<? extends SocketMessage> messageClass = (Class<? extends SocketMessage>) this.messageTypeRegistry.getMessageClass(messageTypeId);
|
||||
|
||||
if (messageClass == null) {
|
||||
this.dhSupport.warning("Unknown message type " + messageTypeId + " with body " + Utils.bytesToHex(data));
|
||||
return null;
|
||||
}
|
||||
|
||||
//this.dhSupport.info("Looks like a " + messageClass.getSimpleName());
|
||||
|
||||
SocketMessage message;
|
||||
|
||||
try {
|
||||
message = messageClass.getConstructor().newInstance();
|
||||
|
||||
if (message instanceof TrackableSocketMessage) {
|
||||
TrackableSocketMessage trackable = (TrackableSocketMessage) message;
|
||||
|
||||
trackable.setTracker(decoder.readInt());
|
||||
|
||||
//this.dhSupport.info("Message is trackable: " + trackable.getTracker());
|
||||
}
|
||||
|
||||
message.decode(decoder);
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to init message class: " + exception.getClass() + " - " + exception.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public void sendSocketMessage(Channel socket, SocketMessage message)
|
||||
{
|
||||
int messageTypeId = this.messageTypeRegistry.getMessageTypeId(message.getClass());
|
||||
|
||||
if (messageTypeId == -1) {
|
||||
this.dhSupport.warning("Trying to send unknown message type: " + message.getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
|
||||
try {
|
||||
Encoder encoder = new Encoder();
|
||||
message.encode(encoder);
|
||||
data = encoder.toByteArray();
|
||||
} catch (Exception exception) {
|
||||
this.dhSupport.warning("Failed to encode " + message.getClass() + ": " + exception.getClass() + " - " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
int tracker = -1;
|
||||
|
||||
if (message instanceof TrackableSocketMessage) {
|
||||
TrackableSocketMessage trackable = (TrackableSocketMessage) message;
|
||||
|
||||
tracker = trackable.getTracker();
|
||||
}
|
||||
|
||||
// TODO: Keep track of trackable messages.
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
encoder.writeInt(data.length + 2 + (tracker == -1 ? 0 : 4));
|
||||
encoder.writeShort(messageTypeId);
|
||||
|
||||
if (tracker != -1) {
|
||||
encoder.writeInt(tracker);
|
||||
}
|
||||
|
||||
encoder.write(data);
|
||||
|
||||
byte[] fullMessage = encoder.toByteArray();
|
||||
|
||||
//this.dhSupport.info("Sending: " + Utils.bytesToHex(fullMessage));
|
||||
|
||||
socket.write(Unpooled.wrappedBuffer(fullMessage));
|
||||
socket.flush();
|
||||
}
|
||||
|
||||
public SocketServer getSocketServer()
|
||||
{
|
||||
return this.socketServer;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.handler.message;
|
||||
|
||||
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.handler.PluginMessageHandler;
|
||||
import no.jckf.dhsupport.core.message.plugin.HelloPluginMessage;
|
||||
import no.jckf.dhsupport.core.message.plugin.ServerConnectInfoMessage;
|
||||
|
||||
public class PluginHandshakeHandler
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected PluginMessageHandler pluginMessageHandler;
|
||||
|
||||
public PluginHandshakeHandler(DhSupport dhSupport, PluginMessageHandler pluginMessageHandler)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.pluginMessageHandler = pluginMessageHandler;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.pluginMessageHandler.getEventBus().registerHandler(HelloPluginMessage.class, (helloMessage) -> {
|
||||
Configuration config = this.dhSupport.getConfig();
|
||||
|
||||
ServerConnectInfoMessage response = new ServerConnectInfoMessage();
|
||||
response.setAddress(config.getString(DhsConfig.HOST));
|
||||
response.setPort(config.getInt(DhsConfig.PORT));
|
||||
|
||||
this.pluginMessageHandler.sendPluginMessage(helloMessage.getSender(), response);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,107 +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.handler.message;
|
||||
|
||||
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.handler.SocketMessageHandler;
|
||||
import no.jckf.dhsupport.core.message.socket.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SocketHandshakeHandler
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected SocketMessageHandler socketMessageHandler;
|
||||
|
||||
public SocketHandshakeHandler(DhSupport dhSupport, SocketMessageHandler socketMessageHandler)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.socketMessageHandler = socketMessageHandler;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.socketMessageHandler.getEventBus().registerHandler(HelloSocketMessage.class, (helloMessage) -> {
|
||||
if (helloMessage.getVersion() != this.socketMessageHandler.protocolVersion) {
|
||||
CloseSocketMessage response = new CloseSocketMessage();
|
||||
response.setMessage("Incompatible Distant Horizons version. Server speaks protocol version " + this.socketMessageHandler.protocolVersion + ", but client sent protocol version " + helloMessage.getVersion());
|
||||
this.socketMessageHandler.sendSocketMessage(helloMessage.getSender(), response);
|
||||
return;
|
||||
}
|
||||
|
||||
HelloSocketMessage response = new HelloSocketMessage();
|
||||
response.setVersion(this.socketMessageHandler.protocolVersion);
|
||||
this.socketMessageHandler.sendSocketMessage(helloMessage.getSender(), response);
|
||||
});
|
||||
|
||||
this.socketMessageHandler.getEventBus().registerHandler(PlayerUuidSocketMessage.class, (uuidMessage) -> {
|
||||
UUID uuid = uuidMessage.getUuid();
|
||||
|
||||
if (this.dhSupport.getSocketIdByPlayerUuid(uuid) != null) {
|
||||
CloseSocketMessage response = new CloseSocketMessage();
|
||||
response.setMessage("UUID hijack attempt.");
|
||||
this.socketMessageHandler.sendSocketMessage(uuidMessage.getSender(), response);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dhSupport.associatePlayerAndSocket(uuidMessage.getUuid(), uuidMessage.getSender().id());
|
||||
|
||||
AckSocketMessage response = new AckSocketMessage();
|
||||
response.isResponseTo(uuidMessage);
|
||||
this.socketMessageHandler.sendSocketMessage(uuidMessage.getSender(), response);
|
||||
});
|
||||
|
||||
this.socketMessageHandler.getEventBus().registerHandler(PlayerConfigSocketMessage.class, (configMessage) -> {
|
||||
Configuration dhsConfig = this.dhSupport.getConfig();
|
||||
Configuration clientConfig = configMessage.toConfiguration();
|
||||
|
||||
// This is not very flexible, but will do for now.
|
||||
for (String key : DhsConfig.getKeys()) {
|
||||
//this.dhSupport.getLogger().info("Config key " + key + ":");
|
||||
|
||||
Object dhsValue = dhsConfig.get(key);
|
||||
Object clientValue = clientConfig.get(key);
|
||||
Object keepValue;
|
||||
|
||||
if (dhsValue instanceof Boolean dhsBool && clientValue instanceof Boolean clientBool) {
|
||||
keepValue = dhsBool && clientBool;
|
||||
|
||||
//this.dhSupport.getLogger().info(" Server " + (dhsBool ? "Y" : "N") + " or client " + (clientBool ? "Y" : "N") + " = " + ((boolean) keepValue ? "Y" : "N"));
|
||||
} else if (dhsValue instanceof Integer dhsInt && clientValue instanceof Integer clientInt) {
|
||||
keepValue = dhsInt < clientInt ? dhsInt : clientInt;
|
||||
|
||||
//this.dhSupport.getLogger().info(" Server " + dhsInt + " or client " + clientInt + " = " + keepValue);
|
||||
} else {
|
||||
keepValue = null;
|
||||
|
||||
//this.dhSupport.getLogger().info(" Uhh... 😵💫");
|
||||
}
|
||||
|
||||
clientConfig.set(key, keepValue);
|
||||
}
|
||||
|
||||
// TODO: Store the resulting config in some sort of context object for this player.
|
||||
|
||||
this.socketMessageHandler.sendSocketMessage(configMessage.getSender(), configMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,68 +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.handler.message;
|
||||
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
import no.jckf.dhsupport.core.handler.SocketMessageHandler;
|
||||
import no.jckf.dhsupport.core.message.socket.FullDataRequestSocketMessage;
|
||||
import no.jckf.dhsupport.core.message.socket.FullDataResponseSocketMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SocketLodHandler
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected SocketMessageHandler socketMessageHandler;
|
||||
|
||||
public SocketLodHandler(DhSupport dhSupport, SocketMessageHandler socketMessageHandler)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.socketMessageHandler = socketMessageHandler;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
this.socketMessageHandler.getEventBus().registerHandler(FullDataRequestSocketMessage.class, (requestMessage) -> {
|
||||
UUID playerUuid = this.dhSupport.getPlayerUuidBySocketId(requestMessage.getSender().id());
|
||||
|
||||
// This happens when a player disconnects with pending messages.
|
||||
if (playerUuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Some sort of Player wrapper or interface object. Bukkit classes should not be imported here.
|
||||
UUID worldUuid = Bukkit.getPlayer(playerUuid).getWorld().getUID();
|
||||
|
||||
// TODO: Verify that the requested section is withing the player's view distance.
|
||||
|
||||
this.dhSupport.getLodData(worldUuid, requestMessage.getPosition())
|
||||
.thenAccept((lodData) -> {
|
||||
FullDataResponseSocketMessage response = new FullDataResponseSocketMessage();
|
||||
response.isResponseTo(requestMessage);
|
||||
response.setData(lodData);
|
||||
|
||||
this.socketMessageHandler.sendSocketMessage(requestMessage.getSender(), response);
|
||||
});
|
||||
});
|
||||
|
||||
//this.socketMessageHandler.getEventBus().registerHandler(GenerationTaskPriorityRequest.class, (requestMessage) -> { });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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.*;
|
||||
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 = 1;//this.worldInterface.getConfig().getInt(DhsConfig.BUILDER_RESOLUTION);
|
||||
int originalStep = yStep;
|
||||
|
||||
boolean scanToSeaLevel = this.worldInterface.getConfig().getBool(DhsConfig.SCAN_TO_SEA_LEVEL, false);
|
||||
boolean underfill = this.worldInterface.getConfig().getBool(DhsConfig.FAST_UNDERFILL, true);
|
||||
boolean includeNonCollidingTopLayer = this.worldInterface.getConfig().getBool(DhsConfig.INCLUDE_NON_COLLIDING_TOP_LAYER, true);
|
||||
boolean sampleBiomes3d = this.worldInterface.getConfig().getBool(DhsConfig.SAMPLE_BIOMES_3D, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>(64);
|
||||
Map<String, Map<String, Integer>> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>(Lod.width * Lod.width);
|
||||
|
||||
List<Beacon> beacons = new ArrayList<>(8);
|
||||
|
||||
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);
|
||||
int hardTopLayer = topLayer;
|
||||
|
||||
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 = "";
|
||||
|
||||
if (!sampleBiomes3d) {
|
||||
biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
}
|
||||
|
||||
List<DataPoint> column = new ArrayList<>(8);
|
||||
|
||||
@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 (this.worldInterface.isBeacon(worldX, highWorldY, worldZ)) {
|
||||
beacons.add(new Beacon(
|
||||
worldX,
|
||||
highWorldY,
|
||||
worldZ,
|
||||
worldInterface.getBeaconColor(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleBiomes3d) {
|
||||
biome = this.worldInterface.getBiomeAt(worldX, highWorldY, worldZ);
|
||||
}
|
||||
|
||||
Map<String, Integer> biomeMap = mapMap.get(biome);
|
||||
if (biomeMap == null) {
|
||||
biomeMap = new HashMap<>();
|
||||
mapMap.put(biome, biomeMap);
|
||||
}
|
||||
|
||||
Integer id = biomeMap.get(material);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, null));
|
||||
id = idMappings.size() - 1;
|
||||
biomeMap.put(material, 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (underfill && previous != null) {
|
||||
previous.setHeight(previous.getStartY() + previous.getHeight());
|
||||
previous.setStartY(0);
|
||||
}
|
||||
|
||||
columns.add(column);
|
||||
}
|
||||
}
|
||||
|
||||
return new Lod(this.worldInterface, this.position, idMappings, columns, beacons);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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.*;
|
||||
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 = 1;//this.worldInterface.getConfig().getInt(DhsConfig.BUILDER_RESOLUTION);
|
||||
int originalStep = yStep;
|
||||
|
||||
boolean includeNonCollidingTopLayer = this.worldInterface.getConfig().getBool(DhsConfig.INCLUDE_NON_COLLIDING_TOP_LAYER, true);
|
||||
boolean performUnderglowHack = this.worldInterface.getConfig().getBool(DhsConfig.PERFORM_UNDERGLOW_HACK, false);
|
||||
boolean sampleBiomes3d = this.worldInterface.getConfig().getBool(DhsConfig.SAMPLE_BIOMES_3D, false);
|
||||
|
||||
List<IdMapping> idMappings = new ArrayList<>(64);
|
||||
Map<String, Map<String, Map<String, Integer>>> mapMap = new HashMap<>();
|
||||
|
||||
List<List<DataPoint>> columns = new ArrayList<>(Lod.width * Lod.width);
|
||||
|
||||
List<Beacon> beacons = new ArrayList<>(8);
|
||||
|
||||
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);
|
||||
int hardTopLayer = topLayer;
|
||||
|
||||
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 = "";
|
||||
|
||||
if (!sampleBiomes3d) {
|
||||
biome = this.worldInterface.getBiomeAt(worldX, worldZ);
|
||||
}
|
||||
|
||||
List<DataPoint> column = new ArrayList<>(32);
|
||||
|
||||
@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);
|
||||
|
||||
if (this.worldInterface.isBeacon(worldX, highWorldY, worldZ)) {
|
||||
beacons.add(new Beacon(
|
||||
worldX,
|
||||
highWorldY,
|
||||
worldZ,
|
||||
worldInterface.getBeaconColor(
|
||||
worldX,
|
||||
highWorldY,
|
||||
worldZ
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
if (sampleBiomes3d) {
|
||||
biome = this.worldInterface.getBiomeAt(worldX, highWorldY, worldZ);
|
||||
}
|
||||
|
||||
Map<String, Map<String, Integer>> biomeMap = mapMap.get(biome);
|
||||
if (biomeMap == null) {
|
||||
biomeMap = new HashMap<>();
|
||||
mapMap.put(biome, biomeMap);
|
||||
}
|
||||
|
||||
Map<String, Integer> materialMap = biomeMap.get(material);
|
||||
if (materialMap == null) {
|
||||
materialMap = new HashMap<>();
|
||||
biomeMap.put(material, materialMap);
|
||||
}
|
||||
|
||||
String blockState = this.worldInterface.getBlockStateAsStringAt(worldX, highWorldY, worldZ);
|
||||
|
||||
Integer id = materialMap.get(blockState);
|
||||
|
||||
if (id == null) {
|
||||
idMappings.add(new IdMapping(biome, material, this.worldInterface.getBlockPropertiesAt(worldX, highWorldY, worldZ)));
|
||||
id = idMappings.size() - 1;
|
||||
materialMap.put(blockState, 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);
|
||||
|
||||
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;
|
||||
|
||||
if (performUnderglowHack && previous != null && previous.getHeight() > 2) {
|
||||
// Retract 2 block. We need a gap between the end of the top data point and the bottom one to prevent DH from merging them.
|
||||
previous.setStartY(previous.getStartY() + 2);
|
||||
previous.setHeight(previous.getHeight() - 2);
|
||||
|
||||
DataPoint bottomBlock = new DataPoint();
|
||||
bottomBlock.setMappingId(previous.getMappingId());
|
||||
bottomBlock.setStartY(relativeY + 1);
|
||||
bottomBlock.setHeight(1);
|
||||
bottomBlock.setSkyLight(this.worldInterface.getSkyLightAt(worldX, lowWorldY, worldZ));
|
||||
bottomBlock.setBlockLight(this.worldInterface.getBlockLightAt(worldX, lowWorldY, worldZ));
|
||||
|
||||
column.add(bottomBlock);
|
||||
|
||||
previous = bottomBlock;
|
||||
}
|
||||
}
|
||||
} 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, beacons);
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -16,25 +16,23 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.lodbuilders;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.dataobject.Lod;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
import no.jckf.dhsupport.core.world.WorldInterface;
|
||||
|
||||
public class PartialUpdateSocketMessage extends SocketMessage
|
||||
public abstract class LodBuilder
|
||||
{
|
||||
protected WorldInterface worldInterface;
|
||||
|
||||
protected SectionPosition position;
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeObject(this.position);
|
||||
encoder.writeInt(0); // Data length.
|
||||
//writer.write(...) // Data.
|
||||
}
|
||||
|
||||
public void setPosition(SectionPosition position)
|
||||
public LodBuilder(WorldInterface worldInterface, SectionPosition position)
|
||||
{
|
||||
this.worldInterface = worldInterface;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public abstract Lod generate();
|
||||
}
|
||||
@@ -25,6 +25,11 @@ import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public abstract class Message implements Encodable, Decodable
|
||||
{
|
||||
public int bufferSize()
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
+37
-37
@@ -1,37 +1,37 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class HelloPluginMessage extends PluginMessage
|
||||
{
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class CancelMessage extends TrackablePluginMessage
|
||||
{
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -16,22 +16,34 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class CancelSocketMessage extends TrackableSocketMessage
|
||||
public class CloseReasonMessage extends PluginMessage
|
||||
{
|
||||
protected String reason;
|
||||
|
||||
public void setReason(String reason)
|
||||
{
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
|
||||
encoder.writeString(this.reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
|
||||
this.reason = decoder.readString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class ExceptionMessage extends TrackablePluginMessage
|
||||
{
|
||||
public static int TYPE_RATE_LIMITED = 0;
|
||||
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;
|
||||
|
||||
protected String message;
|
||||
|
||||
public void setTypeId(int typeId)
|
||||
{
|
||||
this.typeId = typeId;
|
||||
}
|
||||
|
||||
public int getTypeId()
|
||||
{
|
||||
return this.typeId;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return this.message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.typeId);
|
||||
encoder.writeShortString(this.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.typeId = decoder.readInt();
|
||||
this.message = decoder.readShortString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class FullDataChunkMessage extends PluginMessage
|
||||
{
|
||||
protected int bufferId;
|
||||
|
||||
protected byte[] data;
|
||||
|
||||
protected int offset;
|
||||
|
||||
protected int length;
|
||||
|
||||
protected boolean isFirst = true;
|
||||
|
||||
public void setBufferId(int bufferId)
|
||||
{
|
||||
this.bufferId = bufferId;
|
||||
}
|
||||
|
||||
public int getBufferId()
|
||||
{
|
||||
return this.bufferId;
|
||||
}
|
||||
|
||||
public void setData(byte[] data, int offset, int length)
|
||||
{
|
||||
this.data = data;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public byte[] getData()
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setIsFirst(boolean isFirst)
|
||||
{
|
||||
this.isFirst = isFirst;
|
||||
}
|
||||
|
||||
public boolean getIsFirst()
|
||||
{
|
||||
return this.isFirst;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return 32 + this.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.bufferId);
|
||||
encoder.writeInt(this.length);
|
||||
encoder.write(this.data, this.offset, this.length);
|
||||
encoder.writeBoolean(this.isFirst);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class FullDataPartialUpdateMessage extends PluginMessage
|
||||
{
|
||||
protected String levelKey;
|
||||
|
||||
protected int bufferId;
|
||||
|
||||
protected byte[] beacons;
|
||||
|
||||
public void setLevelKey(String levelKey)
|
||||
{
|
||||
this.levelKey = levelKey;
|
||||
}
|
||||
|
||||
public String getLevelKey()
|
||||
{
|
||||
return levelKey;
|
||||
}
|
||||
|
||||
public void setBufferId(int bufferId)
|
||||
{
|
||||
this.bufferId = bufferId;
|
||||
}
|
||||
|
||||
public int getBufferId()
|
||||
{
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
public void setBeacons(byte[] beacons)
|
||||
{
|
||||
this.beacons = beacons;
|
||||
}
|
||||
|
||||
public byte[] getBeacons()
|
||||
{
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return 32 + this.beacons.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeShortString(this.levelKey);
|
||||
encoder.writeInt(this.bufferId);
|
||||
encoder.write(this.beacons);
|
||||
}
|
||||
}
|
||||
+35
-10
@@ -16,32 +16,57 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FullDataRequestSocketMessage extends TrackableSocketMessage
|
||||
public class FullDataSourceRequestMessage extends TrackablePluginMessage
|
||||
{
|
||||
protected String levelName;
|
||||
protected String worldName;
|
||||
|
||||
protected SectionPosition position;
|
||||
|
||||
@Nullable
|
||||
protected Integer checksum;
|
||||
protected Long timestamp;
|
||||
|
||||
public void setWorldName(String worldName)
|
||||
{
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
public String getWorldName()
|
||||
{
|
||||
return this.worldName;
|
||||
}
|
||||
|
||||
public void setPosition(SectionPosition position)
|
||||
{
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public SectionPosition getPosition()
|
||||
{
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Long getTimestamp()
|
||||
{
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.levelName = decoder.readShortString();
|
||||
this.position = decoder.readObject(SectionPosition.class);
|
||||
this.checksum = decoder.readOptional(decoder::readInt);
|
||||
this.worldName = decoder.readShortString();
|
||||
|
||||
this.position = new SectionPosition();
|
||||
this.position.decode(decoder);
|
||||
|
||||
this.timestamp = decoder.readOptional(decoder::readLong);
|
||||
}
|
||||
}
|
||||
+65
-48
@@ -1,48 +1,65 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class ServerConnectInfoMessage extends PluginMessage
|
||||
{
|
||||
protected String address;
|
||||
|
||||
protected int port;
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
if (encoder.writeOptional(this.address)) {
|
||||
encoder.writeString(this.address);
|
||||
}
|
||||
|
||||
encoder.writeShort(this.port);
|
||||
}
|
||||
|
||||
public void setAddress(String address)
|
||||
{
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public void setPort(int port)
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FullDataSourceResponseMessage extends TrackablePluginMessage
|
||||
{
|
||||
protected Integer bufferId;
|
||||
|
||||
protected byte[] beacons;
|
||||
|
||||
public void setBufferId(@Nullable Integer bufferId)
|
||||
{
|
||||
this.bufferId = bufferId;
|
||||
}
|
||||
|
||||
public int getBufferId()
|
||||
{
|
||||
return this.bufferId;
|
||||
}
|
||||
|
||||
public void setBeacons(byte[] beacons)
|
||||
{
|
||||
this.beacons = beacons;
|
||||
}
|
||||
|
||||
public byte[] getBeacons()
|
||||
{
|
||||
return this.beacons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bufferSize()
|
||||
{
|
||||
return this.bufferId == null ? super.bufferSize() : 16 + this.beacons.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
if (encoder.writeOptional(this.bufferId)) {
|
||||
encoder.writeInt(this.bufferId);
|
||||
encoder.write(this.beacons);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class LevelInitMessage extends PluginMessage // TODO: Should probably eventually be a TrackablePluginMessage
|
||||
{
|
||||
protected String namespacedKey;
|
||||
|
||||
protected String serverKey;
|
||||
|
||||
protected String levelKey;
|
||||
|
||||
protected long time;
|
||||
|
||||
public void setNamespacedKey(String namespacedKey)
|
||||
{
|
||||
this.namespacedKey = namespacedKey;
|
||||
}
|
||||
|
||||
public String getNamespacedKey()
|
||||
{
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
public void setServerKey(String serverKey)
|
||||
{
|
||||
this.serverKey = serverKey;
|
||||
}
|
||||
|
||||
public String getServerKey()
|
||||
{
|
||||
return this.serverKey;
|
||||
}
|
||||
|
||||
public void setLevelKey(String levelKey)
|
||||
{
|
||||
this.levelKey = levelKey;
|
||||
}
|
||||
|
||||
public String getLevelKey()
|
||||
{
|
||||
return this.levelKey;
|
||||
}
|
||||
|
||||
public void setTime(long time)
|
||||
{
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getTime()
|
||||
{
|
||||
return this.time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeShortString(this.namespacedKey);
|
||||
encoder.writeShortString(this.serverKey);
|
||||
encoder.writeShortString(this.levelKey);
|
||||
encoder.writeLong(this.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.namespacedKey = decoder.readShortString();
|
||||
this.serverKey = decoder.readShortString();
|
||||
this.levelKey = decoder.readShortString();
|
||||
this.time = decoder.readLong();
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -16,22 +16,22 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class AckSocketMessage extends TrackableSocketMessage
|
||||
public class LevelInitRequestMessage extends PluginMessage
|
||||
{
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
protected String worldKey;
|
||||
|
||||
public String getWorldKey()
|
||||
{
|
||||
return worldKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
|
||||
this.worldKey = decoder.readShortString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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.message.plugin;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
|
||||
public class RemotePlayerConfigMessage extends PluginMessage
|
||||
{
|
||||
public static String[] KEYS = {
|
||||
DhsConfig.DISTANT_GENERATION_ENABLED,
|
||||
DhsConfig.RENDER_DISTANCE,
|
||||
DhsConfig.BORDER_CENTER_X,
|
||||
DhsConfig.BORDER_CENTER_Z,
|
||||
DhsConfig.BORDER_RADIUS,
|
||||
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;
|
||||
|
||||
protected int fullDataRequestConcurrencyLimit;
|
||||
|
||||
protected boolean realTimeUpdatesEnabled;
|
||||
|
||||
protected int realTimeUpdateRadius;
|
||||
|
||||
protected boolean loginDataSyncEnabled;
|
||||
|
||||
protected int loginDataSyncRadius;
|
||||
|
||||
protected int loginDataSyncRcLimit;
|
||||
|
||||
protected int maxDataTransferSpeed;
|
||||
|
||||
protected int borderCenterX;
|
||||
|
||||
protected int borderCenterZ;
|
||||
|
||||
protected int borderRadius;
|
||||
|
||||
public void setRenderDistance(int distance)
|
||||
{
|
||||
this.renderDistance = distance;
|
||||
}
|
||||
|
||||
public int getRenderDistance()
|
||||
{
|
||||
return this.renderDistance;
|
||||
}
|
||||
|
||||
public void setDistantGenerationEnabled(boolean enabled)
|
||||
{
|
||||
this.distantGenerationEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean getDistantGenerationEnabled()
|
||||
{
|
||||
return this.distantGenerationEnabled;
|
||||
}
|
||||
|
||||
public void setFullDataRequestConcurrencyLimit(int limit)
|
||||
{
|
||||
this.fullDataRequestConcurrencyLimit = limit;
|
||||
}
|
||||
|
||||
public int getFullDataRequestConcurrencyLimit()
|
||||
{
|
||||
return this.fullDataRequestConcurrencyLimit;
|
||||
}
|
||||
|
||||
public void setRealTimeUpdatesEnabled(boolean enabled)
|
||||
{
|
||||
this.realTimeUpdatesEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isRealTimeUpdatesEnabled()
|
||||
{
|
||||
return this.realTimeUpdatesEnabled;
|
||||
}
|
||||
|
||||
public void setRealTimeUpdateRadius(int radius)
|
||||
{
|
||||
this.realTimeUpdateRadius = radius;
|
||||
}
|
||||
|
||||
public int getRealTimeUpdateRadius()
|
||||
{
|
||||
return this.realTimeUpdateRadius;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncEnabled(boolean enabled)
|
||||
{
|
||||
this.loginDataSyncEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean getLoginDataSyncEnabled()
|
||||
{
|
||||
return this.loginDataSyncEnabled;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncRadius(int radius)
|
||||
{
|
||||
this.loginDataSyncRadius = radius;
|
||||
}
|
||||
|
||||
public int getLoginDataSyncRadius()
|
||||
{
|
||||
return this.loginDataSyncRadius;
|
||||
}
|
||||
|
||||
public void setLoginDataSyncRcLimit(int limit)
|
||||
{
|
||||
this.loginDataSyncRcLimit = limit;
|
||||
}
|
||||
|
||||
public int getLoginDataSyncRcLimit()
|
||||
{
|
||||
return this.loginDataSyncRcLimit;
|
||||
}
|
||||
|
||||
public void setMaxDataTransferSpeed(int speed)
|
||||
{
|
||||
this.maxDataTransferSpeed = speed;
|
||||
}
|
||||
|
||||
public int getMaxDataTransferSpeed()
|
||||
{
|
||||
return this.maxDataTransferSpeed;
|
||||
}
|
||||
|
||||
public void setBorderCenterX(int borderCenterX)
|
||||
{
|
||||
this.borderCenterX = borderCenterX;
|
||||
}
|
||||
|
||||
public void setBorderCenterZ(int borderCenterZ)
|
||||
{
|
||||
this.borderCenterZ = borderCenterZ;
|
||||
}
|
||||
|
||||
public void setBorderRadius(int borderRadius)
|
||||
{
|
||||
this.borderRadius = borderRadius;
|
||||
}
|
||||
|
||||
public int getBorderCenterX()
|
||||
{
|
||||
return borderCenterX;
|
||||
}
|
||||
|
||||
public int getBorderCenterZ()
|
||||
{
|
||||
return borderCenterZ;
|
||||
}
|
||||
|
||||
public int getBorderRadius()
|
||||
{
|
||||
return borderRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeBoolean(this.distantGenerationEnabled);
|
||||
encoder.writeInt(this.renderDistance);
|
||||
|
||||
encoder.writeInt(this.borderCenterX);
|
||||
encoder.writeInt(this.borderCenterZ);
|
||||
encoder.writeInt(this.borderRadius);
|
||||
|
||||
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.distantGenerationEnabled = decoder.readBoolean();
|
||||
this.renderDistance = decoder.readInt();
|
||||
|
||||
this.borderCenterX = decoder.readInt();
|
||||
this.borderCenterZ = decoder.readInt();
|
||||
this.borderRadius = 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.distantGenerationEnabled = config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED);
|
||||
this.renderDistance = config.getInt(DhsConfig.RENDER_DISTANCE);
|
||||
|
||||
Integer borderCenterX = config.getInt(DhsConfig.BORDER_CENTER_X);
|
||||
Integer borderCenterZ = config.getInt(DhsConfig.BORDER_CENTER_Z);
|
||||
Integer borderRadius = config.getInt(DhsConfig.BORDER_RADIUS);
|
||||
|
||||
this.borderCenterX = borderCenterX == null ? 0 : borderCenterX;
|
||||
this.borderCenterZ = borderCenterZ == null ? 0 : borderCenterZ;
|
||||
this.borderRadius = borderRadius == null ? 0 : borderRadius;
|
||||
|
||||
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.DISTANT_GENERATION_ENABLED, this.distantGenerationEnabled);
|
||||
config.set(DhsConfig.RENDER_DISTANCE, this.renderDistance);
|
||||
|
||||
config.set(DhsConfig.BORDER_CENTER_X, this.borderCenterX);
|
||||
config.set(DhsConfig.BORDER_CENTER_Z, this.borderCenterZ);
|
||||
config.set(DhsConfig.BORDER_RADIUS, this.borderRadius);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -16,9 +16,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package no.jckf.dhsupport.core.message.socket;
|
||||
package no.jckf.dhsupport.core.message.plugin;
|
||||
|
||||
public abstract class TrackableSocketMessage extends SocketMessage
|
||||
public class TrackablePluginMessage extends PluginMessage
|
||||
{
|
||||
protected int tracker;
|
||||
|
||||
@@ -32,8 +32,8 @@ public abstract class TrackableSocketMessage extends SocketMessage
|
||||
return this.tracker;
|
||||
}
|
||||
|
||||
public void isResponseTo(TrackableSocketMessage message)
|
||||
public void isResponseTo(TrackablePluginMessage other)
|
||||
{
|
||||
this.setTracker(message.getTracker());
|
||||
this.setTracker(other.getTracker());
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
|
||||
public class FullDataResponseSocketMessage extends TrackableSocketMessage
|
||||
{
|
||||
protected static final byte formatVersion = 3;
|
||||
|
||||
protected byte[] data;
|
||||
|
||||
public void setData(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeBoolean(true); // True if data is available. False otherwise.
|
||||
encoder.write(data);
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class GenerationTaskPriorityRequest extends TrackableSocketMessage
|
||||
{
|
||||
protected String levelName;
|
||||
|
||||
protected Collection<SectionPosition> sectionPositions;
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.levelName = decoder.readShortString();
|
||||
this.sectionPositions = decoder.readCollection(SectionPosition.class);
|
||||
}
|
||||
|
||||
public void setLevelName(String name)
|
||||
{
|
||||
this.levelName = name;
|
||||
}
|
||||
|
||||
public String getLevelName()
|
||||
{
|
||||
return this.levelName;
|
||||
}
|
||||
|
||||
public void setSectionPositions(Collection<SectionPosition> sectionPositions)
|
||||
{
|
||||
this.sectionPositions = sectionPositions;
|
||||
}
|
||||
|
||||
public Collection<SectionPosition> getSectionPositions()
|
||||
{
|
||||
return this.sectionPositions;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.configuration.Configuration;
|
||||
import no.jckf.dhsupport.core.configuration.DhsConfig;
|
||||
|
||||
public class PlayerConfigSocketMessage extends SocketMessage
|
||||
{
|
||||
protected int renderDistance;
|
||||
|
||||
protected boolean distantGenerationEnabled;
|
||||
|
||||
protected int fullDataRequestConcurrencyLimit;
|
||||
|
||||
protected int generationTaskPriorityRequestRateLimit;
|
||||
|
||||
protected boolean realTimeUpdatedEnabled;
|
||||
|
||||
protected boolean loginDataSyncEnabled;
|
||||
|
||||
protected int loginDataSyncRateConcurrencyLimit;
|
||||
|
||||
protected boolean generateMultipleDimensions;
|
||||
|
||||
@Override
|
||||
public void encode(Encoder encoder)
|
||||
{
|
||||
encoder.writeInt(this.renderDistance);
|
||||
encoder.writeBoolean(this.distantGenerationEnabled);
|
||||
encoder.writeInt(this.fullDataRequestConcurrencyLimit);
|
||||
encoder.writeInt(this.generationTaskPriorityRequestRateLimit);
|
||||
encoder.writeBoolean(this.realTimeUpdatedEnabled);
|
||||
encoder.writeBoolean(this.loginDataSyncEnabled);
|
||||
encoder.writeInt(this.loginDataSyncRateConcurrencyLimit);
|
||||
encoder.writeBoolean(this.generateMultipleDimensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.renderDistance = decoder.readInt();
|
||||
this.distantGenerationEnabled = decoder.readBoolean();
|
||||
this.fullDataRequestConcurrencyLimit = decoder.readInt();
|
||||
this.generationTaskPriorityRequestRateLimit = decoder.readInt();
|
||||
this.realTimeUpdatedEnabled = decoder.readBoolean();
|
||||
this.loginDataSyncEnabled = decoder.readBoolean();
|
||||
this.loginDataSyncRateConcurrencyLimit = decoder.readInt();
|
||||
this.generateMultipleDimensions = decoder.readBoolean();
|
||||
}
|
||||
|
||||
public void fromConfiguration(Configuration config)
|
||||
{
|
||||
this.renderDistance = config.getInt(DhsConfig.RENDER_DISTANCE);
|
||||
this.distantGenerationEnabled = config.getBool(DhsConfig.DISTANT_GENERATION_ENABLED);
|
||||
this.fullDataRequestConcurrencyLimit = config.getInt(DhsConfig.FULL_DATA_REQUEST_CONCURRENCY_LIMIT);
|
||||
this.generationTaskPriorityRequestRateLimit = config.getInt(DhsConfig.GENERATION_TASK_PRIORITY_REQUEST_RATE_LIMIT);
|
||||
this.realTimeUpdatedEnabled = config.getBool(DhsConfig.REAL_TIME_UPDATES_ENABLED);
|
||||
this.loginDataSyncEnabled = config.getBool(DhsConfig.LOGIN_DATA_SYNC_ENABLED);
|
||||
this.loginDataSyncRateConcurrencyLimit = config.getInt(DhsConfig.LOGIN_DATA_SYNC_RC_LIMIT);
|
||||
this.generateMultipleDimensions = config.getBool(DhsConfig.GENERATE_MULTIPLE_DIMENSIONS);
|
||||
}
|
||||
|
||||
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.FULL_DATA_REQUEST_CONCURRENCY_LIMIT, this.fullDataRequestConcurrencyLimit);
|
||||
config.set(DhsConfig.GENERATION_TASK_PRIORITY_REQUEST_RATE_LIMIT, this.generationTaskPriorityRequestRateLimit);
|
||||
config.set(DhsConfig.REAL_TIME_UPDATES_ENABLED, this.realTimeUpdatedEnabled);
|
||||
config.set(DhsConfig.LOGIN_DATA_SYNC_ENABLED, this.loginDataSyncEnabled);
|
||||
config.set(DhsConfig.LOGIN_DATA_SYNC_RC_LIMIT, this.loginDataSyncRateConcurrencyLimit);
|
||||
config.set(DhsConfig.GENERATE_MULTIPLE_DIMENSIONS, this.generateMultipleDimensions);
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +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.message.socket;
|
||||
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerUuidSocketMessage extends TrackableSocketMessage
|
||||
{
|
||||
protected UUID uuid;
|
||||
|
||||
@Override
|
||||
public void decode(Decoder decoder)
|
||||
{
|
||||
this.uuid = new UUID(decoder.readLong(), decoder.readLong());
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public UUID getUuid()
|
||||
{
|
||||
return this.uuid;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +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.message.socket;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import no.jckf.dhsupport.core.message.Message;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class SocketMessage extends Message
|
||||
{
|
||||
Channel sender;
|
||||
|
||||
public void setSender(Channel channel)
|
||||
{
|
||||
this.sender = channel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Channel getSender()
|
||||
{
|
||||
return this.sender;
|
||||
}
|
||||
}
|
||||
@@ -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.scheduling;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface Scheduler
|
||||
{
|
||||
boolean canReadWorldAsync();
|
||||
|
||||
<U> CompletableFuture<U> runOnMainThread(Supplier<U> supplier);
|
||||
|
||||
<U> CompletableFuture<U> runOnMainThreadDelayed(Supplier<U> supplier, long delayTicks);
|
||||
|
||||
<U> CompletableFuture<U> runOnMainThreadDelayed(Supplier<U> supplier, long delayAmount, TimeUnit delayUnit);
|
||||
|
||||
<U> CompletableFuture<U> runOnRegionThread(UUID worldId, int x, int z, Supplier<U> supplier);
|
||||
|
||||
<U> CompletableFuture<U> runOnRegionThreadDelayed(UUID worldId, int x, int z, Supplier<U> supplier, long delayTicks);
|
||||
|
||||
<U> CompletableFuture<U> runOnRegionThreadDelayed(UUID worldId, int x, int z, Supplier<U> supplier, long delayAmount, TimeUnit delayUnit);
|
||||
|
||||
<U> CompletableFuture<U> runOnSeparateThread(Supplier<U> supplier);
|
||||
|
||||
<U> CompletableFuture<U> runOnSeparateThreadDelayed(Supplier<U> supplier, long delayTicks);
|
||||
|
||||
<U> CompletableFuture<U> runOnSeparateThreadDelayed(Supplier<U> supplier, long delayAmount, TimeUnit delayUnit);
|
||||
|
||||
void cancelTasks();
|
||||
|
||||
Executor getExecutor();
|
||||
}
|
||||
@@ -1,64 +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.socketserver;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
|
||||
public class SocketHandler extends ChannelInboundHandlerAdapter
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected ChannelGroup sockets;
|
||||
|
||||
public SocketHandler(DhSupport dhSupport, ChannelGroup sockets)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.sockets = sockets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception
|
||||
{
|
||||
this.sockets.add(ctx.channel());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext context, Object data)
|
||||
{
|
||||
ByteBuf buf = (ByteBuf) data;
|
||||
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.getBytes(buf.readerIndex(), bytes);
|
||||
|
||||
this.dhSupport.getSocketMessageHandler().onSocketMessageReceived(context.channel(), bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext context, Throwable cause)
|
||||
{
|
||||
// Close the connection when an exception is raised.
|
||||
cause.printStackTrace();
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +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.socketserver;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
|
||||
public class SocketInitializer extends ChannelInitializer<SocketChannel>
|
||||
{
|
||||
protected DhSupport dhSupport;
|
||||
|
||||
protected ChannelGroup sockets;
|
||||
|
||||
public SocketInitializer(DhSupport dhSupport, ChannelGroup sockets)
|
||||
{
|
||||
this.dhSupport = dhSupport;
|
||||
this.sockets = sockets;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception
|
||||
{
|
||||
channel.pipeline()
|
||||
.addLast(new LengthFieldBasedFrameDecoder(4 * 1024 * 1024, 0, Integer.BYTES, 0, Integer.BYTES))
|
||||
.addLast(new SocketHandler(this.dhSupport, this.sockets));
|
||||
}
|
||||
}
|
||||
@@ -1,80 +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.socketserver;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import no.jckf.dhsupport.core.DhSupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class SocketServer
|
||||
{
|
||||
protected DhSupport plugin;
|
||||
|
||||
protected EventLoopGroup bossGroup;
|
||||
|
||||
protected EventLoopGroup workerGroup;
|
||||
|
||||
protected ChannelGroup sockets;
|
||||
|
||||
public SocketServer(DhSupport plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public final void startup(int port) throws Exception
|
||||
{
|
||||
this.bossGroup = new NioEventLoopGroup();
|
||||
this.workerGroup = new NioEventLoopGroup();
|
||||
this.sockets = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(this.bossGroup, this.workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new SocketInitializer(this.plugin, this.sockets))
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
|
||||
// Bind and start to accept incoming connections.
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
throw new Exception("Failed to bind to port " + port);
|
||||
}
|
||||
}
|
||||
|
||||
public final void shutdown() throws Exception
|
||||
{
|
||||
this.workerGroup.shutdownGracefully();
|
||||
this.bossGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Channel getSocket(ChannelId id)
|
||||
{
|
||||
return this.sockets.find(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.world;
|
||||
|
||||
public class MaterialRecord
|
||||
{
|
||||
protected final String key;
|
||||
|
||||
protected final boolean isMapped;
|
||||
|
||||
public MaterialRecord(String key, boolean mapped)
|
||||
{
|
||||
this.key = key;
|
||||
this.isMapped = mapped;
|
||||
}
|
||||
|
||||
public boolean isMapped()
|
||||
{
|
||||
return this.isMapped;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return this.key;
|
||||
}
|
||||
}
|
||||
@@ -18,31 +18,90 @@
|
||||
|
||||
package no.jckf.dhsupport.core.world;
|
||||
|
||||
import java.util.Map;
|
||||
import no.jckf.dhsupport.core.configuration.Configurable;
|
||||
|
||||
public interface WorldInterface
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface WorldInterface extends Configurable
|
||||
{
|
||||
WorldInterface newInstance();
|
||||
|
||||
UUID getId();
|
||||
|
||||
String getName();
|
||||
|
||||
String getKey();
|
||||
|
||||
double getCoordinateScale();
|
||||
|
||||
@Nullable Integer getWorldBorderX();
|
||||
|
||||
@Nullable Integer getWorldBorderZ();
|
||||
|
||||
@Nullable Integer getWorldBorderRadius();
|
||||
|
||||
void setDummyMode(boolean enable);
|
||||
|
||||
boolean isDummyMode();
|
||||
|
||||
boolean chunkExists(int x, int z);
|
||||
|
||||
boolean isChunkLoaded(int x, int z);
|
||||
|
||||
boolean loadChunk(int x, int z);
|
||||
|
||||
boolean loadOrGenerateChunk(int x, int z);
|
||||
|
||||
CompletableFuture<Boolean> loadChunkAsync(int x, int z);
|
||||
|
||||
CompletableFuture<Boolean> loadOrGenerateChunkAsync(int x, int z);
|
||||
|
||||
boolean unloadChunk(int x, int z);
|
||||
|
||||
boolean unloadChunkAsync(int x, int z);
|
||||
|
||||
boolean discardChunk(int x, int z);
|
||||
|
||||
int getMinY();
|
||||
|
||||
int getMaxY();
|
||||
|
||||
int getSeaLevel();
|
||||
|
||||
int getSpawnX();
|
||||
|
||||
int getSpawnY();
|
||||
|
||||
int getSpawnZ();
|
||||
|
||||
int getHighestYAt(int x, int z);
|
||||
|
||||
String getBiomeAt(int x, int z);
|
||||
|
||||
String getBiomeAt(int x, int y, int z);
|
||||
|
||||
String getMaterialAt(int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Get the string representation of the block state at the given coordinates.
|
||||
* Block state properties are ordered in ascending order.
|
||||
*/
|
||||
String getBlockStateAsStringAt(int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Get block properties as key/value pairs.
|
||||
* Ordered in ascending order.
|
||||
*/
|
||||
Map<String, String> getBlockPropertiesAt(int x, int y, int z);
|
||||
|
||||
byte getBlockLightAt(int x, int y, int z);
|
||||
|
||||
byte getSkyLightAt(int x, int y, int z);
|
||||
|
||||
boolean isTransparent(int x, int y, int z);
|
||||
boolean isBeacon(int x, int y, int z);
|
||||
|
||||
int getBeaconColor(int x, int y, int z);
|
||||
}
|
||||
+224
-18
@@ -1,32 +1,238 @@
|
||||
# Hostname clients should use to connect to your server.
|
||||
# Optional. DH will default to the same address as the game connection.
|
||||
#host: mc.engine-sim.parts
|
||||
|
||||
# Port number clients should use to connect to your server.
|
||||
# Where should the plugin store its database file?
|
||||
# Required.
|
||||
port: 25049
|
||||
# Default: "{datadir}/data.sqlite"
|
||||
database_path: "{datadir}/data.sqlite"
|
||||
|
||||
# Maximum supported render distance. Clients will be capped to this value.
|
||||
# Maximum supported render distance, given as a radius in number of chunks. Clients will be capped to this value.
|
||||
# Required.
|
||||
render_distance: 128
|
||||
# Default: 1024
|
||||
render_distance: 1024
|
||||
|
||||
# TODO
|
||||
# Are LOD features 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
|
||||
generation_task_priority_request_rate_limit: 50
|
||||
|
||||
# TODO
|
||||
# Send LOD updates to players in real-time?
|
||||
# Required.
|
||||
# Default: true
|
||||
real_time_updates_enabled: true
|
||||
|
||||
# TODO
|
||||
# How close to an update players have to be in order to receive it. Given as a radius in number of chunks.
|
||||
# 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
|
||||
# How far away can a player request updates for during login sync? Given as a radius in number of chunks.
|
||||
# Required.
|
||||
# Default: 128
|
||||
login_data_sync_radius: 128
|
||||
|
||||
# How many login sync requests can a client send at once?
|
||||
# Required.
|
||||
# Default: 50
|
||||
login_data_sync_rc_limit: 50
|
||||
|
||||
# TODO
|
||||
generate_multiple_dimensions: false
|
||||
# Currently unsupported.
|
||||
# Required.
|
||||
# Default: 0
|
||||
max_data_transfer_speed: 0
|
||||
|
||||
# Should new chunks be generated to build LODs?
|
||||
# If you disable this, the plugin will not be able to create LODs in areas where chunks don't already exist.
|
||||
# Optional.
|
||||
# Default: true
|
||||
#generate_new_chunks: false
|
||||
|
||||
# Show warning during startup if chunk generation is enabled?
|
||||
# Optional.
|
||||
# Default: true
|
||||
#generate_new_chunks_warning: false
|
||||
|
||||
# Specs for a fake LOD to send when chunk generation is disabled and LOD requests otherwise would have been rejected.
|
||||
# Optional.
|
||||
# Default: Disabled
|
||||
#dummy_chunk:
|
||||
# - "0-4,bedrock"
|
||||
# - "5-79,stone"
|
||||
# - "80-126,water"
|
||||
|
||||
# How frequently (in seconds) should LODs be updated if world changes are detected?
|
||||
# Required.
|
||||
# Default: 15
|
||||
lod_refresh_interval: 15
|
||||
|
||||
# Select which LOD builder to use.
|
||||
# Valid options are: FullBuilder, FastOverworldBuilder, and None.
|
||||
# Required.
|
||||
# Default: FullBuilder
|
||||
builder_type: FullBuilder
|
||||
|
||||
# Trust world height map.
|
||||
# Disable if you're using a world that was generated with an external tool and your LODs come out broken.
|
||||
# WARNING: This will make LOD building MUCH slower!
|
||||
# Optional.
|
||||
# Default: true
|
||||
#trust_height_map: false
|
||||
|
||||
# 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.
|
||||
# CURRENTLY BROKEN. THIS VALUE IS IGNORED.
|
||||
# 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: true
|
||||
#include_non-colliding_top_layer: false
|
||||
|
||||
# If your server is accessible via multiple different addresses, consider setting a server key to let clients know
|
||||
# that even though the address is different; it's the same server.
|
||||
# This value must be GLOBALLY UNIQUE! No other server on planet Earth should have the same server key as you.
|
||||
# Optional.
|
||||
#server_key:
|
||||
|
||||
# When running DHS on a proxied server, set this to a value that is unique to this specific server.
|
||||
# Optional.
|
||||
#level_key_prefix:
|
||||
|
||||
# Use the vanilla world border of the respective world.
|
||||
# Optional.
|
||||
# Default: true
|
||||
#use_vanilla_world_border: false
|
||||
|
||||
# How many chunks beyond the vanilla world border can we see (if it's enabled)?. If set to auto, it uses the server's
|
||||
# view distance, which effectively means that if standing at the border you can see as far as the server would generate
|
||||
# chunks naturally.
|
||||
# Optional.
|
||||
# Default: auto
|
||||
#vanilla_world_border_expansion: 12
|
||||
|
||||
# List of blocks to replace with other blocks during LOD building.
|
||||
# Useful to hide ugly blocks or valuable resources.
|
||||
# Don't use the "minecraft:" prefix for vanilla blocks.
|
||||
# DO use the namespace prefix for custom blocks.
|
||||
# Optional.
|
||||
# Default: Hide bamboo, seagrass, kelp, and iron + diamond ores.
|
||||
material_map:
|
||||
bamboo: air
|
||||
seagrass: water
|
||||
tall_seagrass: water
|
||||
kelp: water
|
||||
kelp_plant: water
|
||||
iron_ore: stone
|
||||
diamond_ore: stone
|
||||
|
||||
# Vertical distance to offset light data.
|
||||
# This is a hack to work around SPIGOT-7498. If you're somehow on a 1.20.1 server _with_ this patch, set the value to 0.
|
||||
# Optional.
|
||||
# Default:
|
||||
# - On 1.20.1: Minimum build height (-64 for vanilla worlds)
|
||||
# - Any other version: 0
|
||||
#light_offset_hack: 0
|
||||
|
||||
# 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
|
||||
|
||||
# Should biome be checked for every data point, or just once per column?
|
||||
# Enable if your world has 3d/vertical biomes.
|
||||
# Optional.
|
||||
# Default: false
|
||||
#sample_biomes_3d: true
|
||||
|
||||
# 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
|
||||
|
||||
# When using the FastOverworldBuilder, should the bottom data point be stretched to the lowest permitted build
|
||||
# height? This will make the LODs look more complete, and gives a theoretical boost to render performance.
|
||||
# Optional.
|
||||
# Default: true
|
||||
#fast_underfill: false
|
||||
|
||||
# Limit LOD generation to within this area. Alle three values are in blocks.
|
||||
# Optional.
|
||||
#border_center_x: 0
|
||||
#border_center_z: 0
|
||||
#border_radius: 5000
|
||||
|
||||
world_nether:
|
||||
# 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.
|
||||
render_distance: 64
|
||||
|
||||
# This is a hack to prevent light from shining through ceilings, mostly noticeable on the nether ceiling.
|
||||
# Only works with the FullBuilder, should only be used with builder_resolution=1, and does have some side effects.
|
||||
#perform_underglow_hack: true
|
||||
#builder_resolution: 1
|
||||
|
||||
# List of events that should trigger LOD updates.
|
||||
# Required.
|
||||
# Default: A bunch (:
|
||||
update_events:
|
||||
- org.bukkit.event.block.BlockPlaceEvent
|
||||
- org.bukkit.event.block.BlockBreakEvent
|
||||
# - org.bukkit.event.block.TNTPrimeEvent
|
||||
- org.bukkit.event.block.BlockExplodeEvent
|
||||
# - org.bukkit.event.block.BlockIgniteEvent
|
||||
# - org.bukkit.event.block.BlockBurnEvent
|
||||
# - org.bukkit.event.block.LeavesDecayEvent
|
||||
# - org.bukkit.event.block.BlockFadeEvent
|
||||
# - org.bukkit.event.block.BlockGrowEvent
|
||||
# - org.bukkit.event.block.MoistureChangeEvent
|
||||
# - org.bukkit.event.block.BlockRedstoneEvent
|
||||
- org.bukkit.event.block.BlockPistonExtendEvent
|
||||
- org.bukkit.event.block.BlockPistonRetractEvent
|
||||
# - org.bukkit.event.block.SpongeAbsorbEvent
|
||||
# - org.bukkit.event.inventory.FurnaceBurnEvent
|
||||
- org.bukkit.event.world.StructureGrowEvent
|
||||
- org.bukkit.event.world.ChunkPopulateEvent
|
||||
|
||||
# Number of worker threads used to generate LODs.
|
||||
# Required.
|
||||
# Default: 4
|
||||
scheduler_threads: 4
|
||||
|
||||
# Show a message to players who join if they don't have Distant Horizons installed?
|
||||
# Optional.
|
||||
# Default: Disabled
|
||||
#ad_message:
|
||||
# - "§aThis server supports §bDistant Horizons§a!"
|
||||
# - "§aVisit §nhttp://dh-mod.com/§r§a to learn more."
|
||||
|
||||
# If a user uses /dh should we give them a help text?
|
||||
# Optional.
|
||||
# Default: true
|
||||
#enable_dh_command_help_text: false
|
||||
|
||||
# Should we notify server admins when a new version of the plugin is available?
|
||||
# Optional.
|
||||
# Default: true
|
||||
#check_for_updates: false
|
||||
|
||||
# Enable debug logging?
|
||||
# Optional.
|
||||
# Default: false
|
||||
#debug: true
|
||||
|
||||
# Don't touch :)
|
||||
config_version: 10
|
||||
@@ -1,4 +1,16 @@
|
||||
name: DHSupport
|
||||
version: 0.1.0-SNAPSHOT
|
||||
main: no.jckf.dhsupport.bukkit.DhSupportBukkitPlugin
|
||||
api-version: 1.20
|
||||
version: ${version}
|
||||
main: ${mainClass}
|
||||
api-version: ${mcApiVersion}
|
||||
prefix: DHS
|
||||
folia-supported: true
|
||||
author: Jckf
|
||||
commands:
|
||||
dhs:
|
||||
description: Manage DH Support.
|
||||
usage: /dhs
|
||||
permission: distant_horizons.admin
|
||||
dh:
|
||||
description: Not used.
|
||||
usage: /dh
|
||||
permission: distant_horizons.admin
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.dataobject.DataPoint;
|
||||
|
||||
public class DataPointTest extends TestCase
|
||||
{
|
||||
private DataPoint dataPoint = new DataPoint();
|
||||
|
||||
public void testMappingId()
|
||||
{
|
||||
int randomNumber = (int) Math.floor(Math.random() * 100);
|
||||
|
||||
this.dataPoint.setMappingId(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getMappingId());
|
||||
}
|
||||
|
||||
public void testMappingIdAgain()
|
||||
{
|
||||
this.testMappingId();
|
||||
}
|
||||
|
||||
public void testHeight()
|
||||
{
|
||||
int randomNumber = (int) Math.floor(Math.random() * 10);
|
||||
|
||||
this.dataPoint.setHeight(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getHeight());
|
||||
}
|
||||
|
||||
public void testHeightAgain()
|
||||
{
|
||||
this.testHeight();
|
||||
}
|
||||
|
||||
public void testStartY()
|
||||
{
|
||||
int randomNumber = (int) Math.floor(Math.random() * 100);
|
||||
|
||||
this.dataPoint.setStartY(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getStartY());
|
||||
}
|
||||
|
||||
public void testHighStartY()
|
||||
{
|
||||
int randomNumber = 4000; // Chosen by fair dice roll. Guaranteed to be random.
|
||||
|
||||
this.dataPoint.setStartY(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getStartY());
|
||||
}
|
||||
|
||||
public void testStartYAgain()
|
||||
{
|
||||
this.testStartY();
|
||||
}
|
||||
|
||||
public void testSkyLight()
|
||||
{
|
||||
byte randomNumber = (byte) Math.floor(Math.random() * 15);
|
||||
|
||||
this.dataPoint.setSkyLight(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getSkyLight());
|
||||
}
|
||||
|
||||
public void testSkyLightAgain()
|
||||
{
|
||||
this.testSkyLight();
|
||||
}
|
||||
|
||||
public void testBlockLight()
|
||||
{
|
||||
byte randomNumber = (byte) Math.floor(Math.random() * 15);
|
||||
|
||||
this.dataPoint.setBlockLight(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.dataPoint.getBlockLight());
|
||||
}
|
||||
|
||||
public void testBlockLightAgain()
|
||||
{
|
||||
this.testBlockLight();
|
||||
}
|
||||
|
||||
public void testEncodeDecode()
|
||||
{
|
||||
int mappingId = (int) Math.floor(Math.random() * 100);
|
||||
int height = (int) Math.floor(Math.random() * 10);
|
||||
int startY = (int) Math.floor(Math.random() * 100);
|
||||
byte skyLight = (byte) Math.floor(Math.random() * 15);
|
||||
byte blockLight = (byte) Math.floor(Math.random() * 15);
|
||||
|
||||
// Set them in reverse order of how they appear in the packed long. This will reveal any accidental overwrites.
|
||||
this.dataPoint.setBlockLight(blockLight);
|
||||
this.dataPoint.setSkyLight(skyLight);
|
||||
this.dataPoint.setStartY(startY);
|
||||
this.dataPoint.setHeight(height);
|
||||
this.dataPoint.setMappingId(mappingId);
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
this.dataPoint.encode(encoder);
|
||||
|
||||
this.dataPoint = new DataPoint();
|
||||
|
||||
Decoder decoder = new Decoder(encoder.toByteArray());
|
||||
|
||||
this.dataPoint.decode(decoder);
|
||||
|
||||
assertEquals("Block light", blockLight, this.dataPoint.getBlockLight());
|
||||
assertEquals("Sky light", skyLight, this.dataPoint.getSkyLight());
|
||||
assertEquals("Start Y", startY, this.dataPoint.getStartY());
|
||||
assertEquals("Height", height, this.dataPoint.getHeight());
|
||||
assertEquals("Mapping ID", mappingId, this.dataPoint.getMappingId());
|
||||
}
|
||||
|
||||
public void testBinary()
|
||||
{
|
||||
this.dataPoint = new DataPoint();
|
||||
|
||||
this.dataPoint.setMappingId(1);
|
||||
Encoder mappingIdEncoder = new Encoder();
|
||||
this.dataPoint.encode(mappingIdEncoder);
|
||||
this.dataPoint.setMappingId(0);
|
||||
|
||||
this.dataPoint.setHeight(1);
|
||||
Encoder heightEncoder = new Encoder();
|
||||
this.dataPoint.encode(heightEncoder);
|
||||
this.dataPoint.setHeight(0);
|
||||
|
||||
this.dataPoint.setStartY(1);
|
||||
Encoder startYEncoder = new Encoder();
|
||||
this.dataPoint.encode(startYEncoder);
|
||||
this.dataPoint.setStartY(0);
|
||||
|
||||
this.dataPoint.setSkyLight((byte) 1);
|
||||
Encoder skyLightEncoder = new Encoder();
|
||||
this.dataPoint.encode(skyLightEncoder);
|
||||
this.dataPoint.setSkyLight((byte) 0);
|
||||
|
||||
this.dataPoint.setBlockLight((byte) 1);
|
||||
Encoder blockLightEncoder = new Encoder();
|
||||
this.dataPoint.encode(blockLightEncoder);
|
||||
this.dataPoint.setBlockLight((byte) 0);
|
||||
|
||||
// | Blk| Sky| Start Y| Height| Mapping ID|
|
||||
assertEquals("Mapping ID", "0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001", Utils.bytesToBin(mappingIdEncoder.toByteArray()));
|
||||
assertEquals("Height", "0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(heightEncoder.toByteArray()));
|
||||
assertEquals("StartY", "0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(startYEncoder.toByteArray()));
|
||||
assertEquals("Sky light", "0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(skyLightEncoder.toByteArray()));
|
||||
assertEquals("Block light", "0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(blockLightEncoder.toByteArray()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import no.jckf.dhsupport.core.Utils;
|
||||
import no.jckf.dhsupport.core.bytestream.Decoder;
|
||||
import no.jckf.dhsupport.core.bytestream.Encoder;
|
||||
import no.jckf.dhsupport.core.dataobject.SectionPosition;
|
||||
|
||||
public class SectionPositionTest extends TestCase
|
||||
{
|
||||
protected SectionPosition sectionPosition = new SectionPosition();
|
||||
|
||||
public void testDetailLevel()
|
||||
{
|
||||
int randomNumber = (int) Math.ceil(Math.random() * 6);
|
||||
|
||||
this.sectionPosition.setDetailLevel(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.sectionPosition.getDetailLevel());
|
||||
}
|
||||
|
||||
public void testDetailLevelAgain()
|
||||
{
|
||||
this.testDetailLevel();
|
||||
}
|
||||
|
||||
public void testPositiveX()
|
||||
{
|
||||
int randomNumber = (int) Math.ceil(Math.random() * 100000);
|
||||
|
||||
this.sectionPosition.setX(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.sectionPosition.getX());
|
||||
}
|
||||
|
||||
public void testNegativeX()
|
||||
{
|
||||
int randomNumber = (int) Math.ceil(0 - Math.random() * 100000);
|
||||
|
||||
this.sectionPosition.setX(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.sectionPosition.getX());
|
||||
}
|
||||
|
||||
public void testXAgain()
|
||||
{
|
||||
this.testPositiveX();
|
||||
}
|
||||
|
||||
public void testPositiveZ()
|
||||
{
|
||||
int randomNumber = (int) Math.ceil(Math.random() * 100000);
|
||||
|
||||
this.sectionPosition.setZ(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.sectionPosition.getZ());
|
||||
}
|
||||
|
||||
public void testNegativeZ()
|
||||
{
|
||||
int randomNumber = (int) Math.ceil(0 - Math.random() * 100000);
|
||||
|
||||
this.sectionPosition.setZ(randomNumber);
|
||||
|
||||
assertEquals(randomNumber, this.sectionPosition.getZ());
|
||||
}
|
||||
|
||||
public void testZAgain()
|
||||
{
|
||||
this.testPositiveZ();
|
||||
}
|
||||
|
||||
public void testEncodeDecode()
|
||||
{
|
||||
int detailLevel = (int) Math.ceil(Math.random() * 6);
|
||||
int x = (int) Math.ceil(Math.random() * 100000);
|
||||
int z = (int) Math.ceil(Math.random() * 100000);
|
||||
|
||||
this.sectionPosition.setDetailLevel(detailLevel);
|
||||
this.sectionPosition.setX(x);
|
||||
this.sectionPosition.setZ(z);
|
||||
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
this.sectionPosition.encode(encoder);
|
||||
|
||||
this.sectionPosition = new SectionPosition();
|
||||
|
||||
Decoder decoder = new Decoder(encoder.toByteArray());
|
||||
|
||||
this.sectionPosition.decode(decoder);
|
||||
|
||||
assertEquals("Detail level", detailLevel, this.sectionPosition.getDetailLevel());
|
||||
assertEquals("X coordinate", x, this.sectionPosition.getX());
|
||||
assertEquals("Z coordinate", z, this.sectionPosition.getZ());
|
||||
}
|
||||
|
||||
public void testBinary()
|
||||
{
|
||||
this.sectionPosition = new SectionPosition();
|
||||
|
||||
this.sectionPosition.setDetailLevel(1);
|
||||
Encoder detailLevelEncoder = new Encoder();
|
||||
this.sectionPosition.encode(detailLevelEncoder);
|
||||
byte[] detailLevelBytes = detailLevelEncoder.toByteArray();
|
||||
this.sectionPosition.setDetailLevel(0);
|
||||
|
||||
this.sectionPosition.setX(1);
|
||||
Encoder xEncoder = new Encoder();
|
||||
this.sectionPosition.encode(xEncoder);
|
||||
byte[] xBytes = xEncoder.toByteArray();
|
||||
this.sectionPosition.setX(0);
|
||||
|
||||
this.sectionPosition.setX(-1);
|
||||
Encoder negativeXEncoder = new Encoder();
|
||||
this.sectionPosition.encode(negativeXEncoder);
|
||||
byte[] negativeXBytes = negativeXEncoder.toByteArray();
|
||||
this.sectionPosition.setX(0);
|
||||
|
||||
this.sectionPosition.setZ(1);
|
||||
Encoder zEncoder = new Encoder();
|
||||
this.sectionPosition.encode(zEncoder);
|
||||
byte[] zBytes = zEncoder.toByteArray();
|
||||
this.sectionPosition.setZ(0);
|
||||
|
||||
this.sectionPosition.setZ(-1);
|
||||
Encoder negativeZEncoder = new Encoder();
|
||||
this.sectionPosition.encode(negativeZEncoder);
|
||||
byte[] negativeZBytes = negativeZEncoder.toByteArray();
|
||||
this.sectionPosition.setZ(0);
|
||||
|
||||
// | Z| X| Detail|
|
||||
assertEquals("Detail level", "0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001", Utils.bytesToBin(detailLevelBytes));
|
||||
assertEquals("X coordinate", "0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000", Utils.bytesToBin(xBytes));
|
||||
assertEquals("Negative X coordinate", "0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 0000 0000", Utils.bytesToBin(negativeXBytes));
|
||||
assertEquals("Z coordinate", "0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(zBytes));
|
||||
assertEquals("Negative Z coordinate", "1111 1111 1111 1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000", Utils.bytesToBin(negativeZBytes));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user