diff --git a/CHANGES_26_2_BLOCK_MAPPINGS.md b/CHANGES_26_2_BLOCK_MAPPINGS.md new file mode 100644 index 000000000..9a7675acb --- /dev/null +++ b/CHANGES_26_2_BLOCK_MAPPINGS.md @@ -0,0 +1,100 @@ +# Dynmap 26.2 block mapping fixes — changelog + +**Context:** On Minecraft 26.2 (Paper), a number of blocks rendered as solid black on the Dynmap +web map, with no error at all in the server log. Root cause: Dynmap silently renders any block +state it has no `texture_1.txt` / `models_1.txt` mapping for as a blank/black tile — there is no +warning when a block is simply *missing* from the mapping data. This mostly affects blocks added +in Minecraft versions newer than the last time these two files were updated (last version tag +present before this fix: `[1.21.9-]`). + +Method used: diffed `assets/minecraft/blockstates/*.json` between the Minecraft 1.21.7, 1.21.11, +and 26.2 client jars to get the exact list of new/changed blocks, then cross-checked every +existing mapped block's real state properties against the current 26.2 blockstate JSON to catch +silent property renames (not just brand-new blocks). + +## Files changed + +- `DynmapCore/src/main/resources/texture_1.txt` +- `DynmapCore/src/main/resources/models_1.txt` +- `DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/*.png` (new texture files, extracted from the vanilla 26.2 client jar — Mojang assets, same as every other vanilla texture already bundled in this resource pack) + +All new entries are tagged `[26.2-]` so they only apply on 26.2+ servers and cannot affect older versions. + +## 1. New blocks added (introduced between 1.21.7 and 26.2), fully mapped + +**Sulfur family** — full block, bricks, chiseled, polished, potent variant, plus stairs/slabs/walls +for the plain, brick, and polished forms (mapped the same way the existing `tuff`/`tuff_bricks` +family already was): +`sulfur`, `sulfur_bricks`, `chiseled_sulfur`, `polished_sulfur`, `potent_sulfur`, +`sulfur_stairs`, `sulfur_slab`, `sulfur_wall`, +`sulfur_brick_stairs`, `sulfur_brick_slab`, `sulfur_brick_wall`, +`polished_sulfur_stairs`, `polished_sulfur_slab`, `polished_sulfur_wall` + +**Cinnabar family** — same treatment as sulfur: +`cinnabar`, `cinnabar_bricks`, `chiseled_cinnabar`, `polished_cinnabar`, +`cinnabar_stairs`, `cinnabar_slab`, `cinnabar_wall`, +`cinnabar_brick_stairs`, `cinnabar_brick_slab`, `cinnabar_brick_wall`, +`polished_cinnabar_stairs`, `polished_cinnabar_slab`, `polished_cinnabar_wall` + +**Sulfur Spike** — `sulfur_spike` (10 model states: base/frustum/middle/tip/tip_merge × up/down). +Mapped as a billboard cross patch, the same technique Dynmap already uses for vanilla +`pointed_dripstone` (added a `sulfur_spike` line right next to the existing `pointed_dripstone` +patchblock definition in `models_1.txt`) — this is Dynmap's accepted approximation for this shape, +not a compromise specific to this fix. + +**Flowers:** +`golden_dandelion` (mapped identically to the existing `dandelion` cross-plant patchblock) and +`potted_golden_dandelion` (mapped identically to `potted_dandelion`). + +**Copper Golem Statue** (8 oxidation/wax variants: `copper_golem_statue`, `exposed_copper_golem_statue`, +`weathered_copper_golem_statue`, `oxidized_copper_golem_statue`, and their `waxed_*` counterparts): +this block has **no real block model** in vanilla — it's drawn entirely through a +BlockEntityRenderer, so there is no faithful patch-based geometry to map. Approximated as a plain +solid cube using the matching existing copper-oxidation-stage texture (`copper_block` / +`exposed_copper` / `weathered_copper` / `oxidized_copper`) so it at least shows up as +copper-colored on the map instead of invisible. **Flagged for anyone reviewing this: a real fix +would need a custom Java `CustomRenderer` class to approximate its actual statue shape.** + +The following blocks from the same 1.21.7→26.2 diff were checked and found **already correctly +mapped** before this fix (no changes needed): all wood `*_shelf` blocks (acacia, bamboo, birch, +cherry, crimson, dark_oak, jungle, mangrove, oak, pale_oak, spruce, warped), and the full copper +decor set (chains, bars, torches, wall torches, lanterns, lightning rods, chests — including all +`waxed_*` variants). + +## 2. Bug fix: `creaking_heart` blockstate property renamed + +`creaking_heart` was already mapped (added at `[1.21.4-]`), but only for its **old** state property +`active:true/false`. At some point before 26.2, Mojang renamed/expanded this to a 3-value property +`creaking_heart_state:awake/dormant/uprooted`. Since the property name itself changed, every +`creaking_heart` on a 26.2 server matched *zero* mapping entries and rendered black. + +Fix: added 9 new `[26.2-]` mapping lines (3 axis × 3 `creaking_heart_state` values) using texture +files that were already bundled in the resource pack but never wired up +(`creaking_heart_awake`/`_dormant` + their `_top` variants). The old `[1.21.4-]` `active:true/false` +entries were **left in place untouched** — they're harmless dead weight on 26.2 (no block state +there has an `active` property anymore) but still needed for correctness on 1.21.4–1.21.11 servers. + +## 3. Verification performed (no other issues found) + +- Extracted **every** `assets/minecraft/blockstates/*.json` from the 26.2 client jar and + cross-referenced all ~550 distinct `(blockId, stateProperty)` pairs currently referenced anywhere + in `texture_1.txt`/`models_1.txt` against the real property names for that block in 26.2 (handling + both the flat `"axis=x,type=y"` variant-key format and the newer `"multipart"`/`"when"` format). + Only flagged mismatch: the intentionally-kept legacy `creaking_heart`/`active` entries described + above — nothing else. +- Confirmed all 92 blocks from the 1.21.7→26.2 blockstate diff are now covered. + +## 4. Investigated, not a real bug (self-resolved) + +A `copper_chest[facing=north,type=single,waterlogged=true] - not enough textures for faces (16 > 6)` +severe log line was observed once. Traced it as far as `TexturePack.java`'s integrity check +(`HDBlockStateTextureMap` / `HDBlockModels.getNeededTextureCount`) and `ChestStateRenderer.java`; +added temporary debug logging to investigate further, but on the next clean server restart (plus a +`/dynmap purgemap` + `radiusrender`) the error did not reproduce at all and the copper chest +rendered correctly. Likely a one-off load-order artifact at plugin boot. Debug logging was removed +again afterward — no net code change here, just noting it in case it resurfaces for someone else. + +## Not covered / left as follow-up + +- **Copper Golem Statue** real geometry (currently a flat-color cube approximation, see above). +- No other known gaps as of this writing (26.2, 2026-07-19). diff --git a/CLAUDE.md b/CLAUDE.md index 60402cd83..29f53d7ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,8 +8,10 @@ Dynmap is a dynamic web mapping plugin/mod for Minecraft servers. It's a multi-p ## Build Commands +This project now spans **three separate Gradle builds**, split by Gradle-version compatibility: + ```bash -# Build all platforms (requires JDK 21 as default) +# Main build: Spigot, all bukkit-helper-*, all fabric-*, DynmapCore/API (Gradle 9.5.1) ./gradlew setup build # Build outputs go to /target directory @@ -20,19 +22,36 @@ Dynmap is a dynamic web mapping plugin/mod for Minecraft servers. It's a multi-p # Run unit tests (DynmapCore only — JUnit 4) ./gradlew :DynmapCore:test +# Modern Forge (1.14.4 - 1.21.11): separate build, Gradle 8.14 - ForgeGradle does not support Gradle 9 +cd forge-build +./gradlew setup build + # Forge 1.12.2 (requires JDK 8 - set JAVA_HOME accordingly) cd oldgradle ./gradlew setup build ``` +**Why three builds:** ForgeGradle (used by `forge-1.14.4` through `forge-1.21.11`) hard-rejects Gradle 9 +("Versions Gradle 9.0 and newer are not supported yet"), so those 10 modules live in `forge-build/` +on Gradle 8.14 (mirroring the existing `oldgradle`/`forge-1.12.2` split). The main build moved to +Gradle 9.5.1 specifically so `bukkit-helper-26-2` (Paper/Spigot 26.2 support) can use +`paperweight-userdev`, which requires Gradle 9+. + **JDK Requirements:** -- Default: JDK 21 +- Default: JDK 21 — run the main build's Gradle daemon on JDK 21, not JDK 25. Confirmed by testing: on JDK 25, `spigot:compileJava` fails with `cannot access NonNull ... class file for org.checkerframework.checker.nullness.qual.NonNull not found` (an unrelated LuckPerms-dependency/`-source 8` cross-compilation quirk specific to JDK 25's javac) - JDK 21 compiles this cleanly. +- `bukkit-helper-26-2` (Paper/Spigot 26.2): requires **JDK 25** to compile against real (unobfuscated) Mojang-mapped classes. This is handled automatically by that module's own Gradle toolchain block (`java { toolchain { languageVersion = JavaLanguageVersion.of(25) } }`) as long as a JDK 25 install is present for Gradle to auto-detect (e.g. under `Program Files\Java`) - no manual `JAVA_HOME` juggling needed once both JDK 21 and JDK 25 are installed. +- `forge-build`: Gradle 8.14 cannot run its daemon on JDK 25 either - use JDK 21 (or any JDK ≤ ~23) there too. +- `fabric-26.2`: unlike `bukkit-helper-26-2`, Fabric Loom needs the Gradle **daemon itself** on JDK 25 for this specific module (`Minecraft 26.2 requires Java 25 but Gradle is using 21`) - a per-module toolchain isn't enough. Run `:fabric-26.2:*` tasks with JDK 25 as the default `java`/`JAVA_HOME`; other tasks in the same invocation (spigot, bukkit-helper-*) still need JDK 21 as noted above, so build them in separate invocations if you need both. - Forge 1.12.2 (oldgradle): JDK 8 strictly required -- Runtime targets: JDK 8 (1.16-), JDK 16 (1.17.x), JDK 17 (1.18-1.20.4), JDK 21 (1.20.5+) +- Runtime targets: JDK 8 (1.16-), JDK 16 (1.17.x), JDK 17 (1.18-1.20.4), JDK 21 (1.20.5-1.21.11), JDK 25 (26.2+) **Build notes:** - `gradle.properties` sets `org.gradle.parallel=false` and `org.gradle.daemon=false` — do not change these - `snakeyaml` is pinned at 1.23 intentionally — newer versions break on Windows-encoded config files +- Shadow plugin is `com.gradleup.shadow` (main build, Gradle 9) — the older `io.github.goooler.shadow` is kept only in `forge-build/` (Gradle 8), since it crashes under Gradle 9's embedded Groovy 4 +- **Shadow include/exclude gotcha (found via real server testing, 2026-07-19):** `com.gradleup.shadow`'s `dependency(...)` notation silently drops the old `'group::'` wildcard (empty artifact name AND version) instead of matching everything in that group - it just matches nothing, with no build error. This broke `DynmapCore`'s bundling of Jetty/javax.servlet/jakarta.xml.bind entirely (the whole embedded web server was missing from the jar) and `spigot`'s bundling of bstats, both silently, both only caught by actually loading the plugin on a real server (`NoClassDefFoundError`). Fixed by spelling out each artifact explicitly with a trailing colon (`'group:artifact:'`, empty version only - that form still works). **If you add a new `include(dependency('group::'))` anywhere in the main build, don't - it will silently do nothing; enumerate the actual artifacts instead**, and verify with `jar tf` on the built output that the relocated package (e.g. `org/dynmap/jetty/...`) actually has classes in it. +- As of Minecraft 26.1+, Mojang ships the server unobfuscated — there is no more "Spigot mappings" reobf layer, so `bukkit-helper-26-2` is written directly against real `net.minecraft.*` class/method names (see that module's source header comment) instead of the obfuscated calls seen in older `bukkit-helper-*` modules +- Fabric follows the same shift: Yarn mappings were discontinued as of 26.1 (nothing left to remap), so `fabric-26.2` uses plugin id `net.fabricmc.fabric-loom` (not `fabric-loom`), has **no `mappings` dependency at all**, uses plain `implementation`/`compileOnly` instead of `modImplementation`/`modCompileOnly`, and has no `remapJar` task - the plain `jar` task is the final artifact. Its own `com.gradleup.shadow`-provided `shadowJar` task is disabled (see build.gradle comment) since it isn't used and would otherwise blow past the 65535-zip-entry limit by bundling the full Fabric/Minecraft runtime classpath. ## Architecture @@ -44,10 +63,10 @@ cd oldgradle - `dynmap-api/` - Bukkit-specific public API **Platform Implementations:** -- `spigot/` - Bukkit/PaperMC implementation (`DynmapPlugin.java`) -- `bukkit-helper-*` - Version-specific NMS code (one per MC version: 1.13-1.21) -- `fabric-*` - Fabric mod implementations (1.14.4-1.21.x) -- `forge-*` - Forge mod implementations (1.14.4-1.21.x); `forge-1.12.2` lives in `oldgradle/` +- `spigot/` - Bukkit/PaperMC implementation (`DynmapPlugin.java`); dispatches to the matching `bukkit-helper-*` at runtime via reflection (`Helper.java`) based on `Server#getVersion()` (legacy `1.x` servers) or `Server#getMinecraftVersion()` (26.x+ servers, called reflectively since it postdates spigot's own ancient compile-time Bukkit API) +- `bukkit-helper-*` - Version-specific NMS code (one per MC version: 1.13-1.21, plus `26-2` for Paper/Spigot 26.2) +- `fabric-*` - Fabric mod implementations (1.14.4-1.21.11, plus `26.2`) +- `forge-*` - Forge mod implementations (1.14.4-1.21.x), built from `forge-build/` (separate Gradle 8.14 build - see Build Commands); `forge-1.12.2` lives in `oldgradle/`/`oldbuilds/` instead ### Dependency Flow ``` diff --git a/DynmapCore/build.gradle b/DynmapCore/build.gradle index d5955eebf..6ac808eec 100644 --- a/DynmapCore/build.gradle +++ b/DynmapCore/build.gradle @@ -8,7 +8,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':DynmapCoreAPI') @@ -68,8 +73,13 @@ shadowJar { include(dependency('com.googlecode.json-simple:json-simple:')) include(dependency('org.yaml:snakeyaml:')) include(dependency('com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:')) - include(dependency('javax.servlet::')) - include(dependency('org.eclipse.jetty::')) + include(dependency('javax.servlet:javax.servlet-api:')) + include(dependency('org.eclipse.jetty:jetty-server:')) + include(dependency('org.eclipse.jetty:jetty-servlet:')) + include(dependency('org.eclipse.jetty:jetty-http:')) + include(dependency('org.eclipse.jetty:jetty-io:')) + include(dependency('org.eclipse.jetty:jetty-util:')) + include(dependency('org.eclipse.jetty:jetty-security:')) include(dependency('org.eclipse.jetty.orbit:javax.servlet:')) include(dependency('org.postgresql:postgresql:')) include(dependency('io.github.linktosriram.s3lite:core:')) @@ -77,8 +87,8 @@ shadowJar { include(dependency('io.github.linktosriram.s3lite:http-client-url-connection:')) include(dependency('io.github.linktosriram.s3lite:http-client-spi:')) include(dependency('io.github.linktosriram.s3lite:util:')) - include(dependency('jakarta.xml.bind::')) - include(dependency('com.sun.xml.bind::')) + include(dependency('jakarta.xml.bind:jakarta.xml.bind-api:')) + include(dependency('com.sun.xml.bind:jaxb-impl:')) include(dependency(':DynmapCoreAPI')) exclude("META-INF/maven/**") exclude("META-INF/services/**") diff --git a/DynmapCore/src/main/resources/models_1.txt b/DynmapCore/src/main/resources/models_1.txt index 5292b2438..56aa28627 100644 --- a/DynmapCore/src/main/resources/models_1.txt +++ b/DynmapCore/src/main/resources/models_1.txt @@ -880,7 +880,7 @@ patchblock:id=tall_seagrass,id=seagrass,patch0=VertX075,patch1=VertX075@90,patch # Oxeye daisy # Brown mushroom # Red mushroom -patchblock:id=dandelion,id=poppy,id=blue_orchid,id=allium,id=azure_bluet,id=red_tulip,id=orange_tulip,id=white_tulip,id=pink_tulip,id=oxeye_daisy,id=brown_mushroom,id=red_mushroom,patch0=VertX1Z0ToX0Z1,patch1=VertX1Z0ToX0Z1@90 +patchblock:id=dandelion,id=poppy,id=blue_orchid,id=allium,id=azure_bluet,id=red_tulip,id=orange_tulip,id=white_tulip,id=pink_tulip,id=oxeye_daisy,id=brown_mushroom,id=red_mushroom,id=golden_dandelion,patch0=VertX1Z0ToX0Z1,patch1=VertX1Z0ToX0Z1@90 # Sunflower modellist:id=%sunflower,state=half:upper,box=0.800000/0.000000/8.000000/false:15.200000/8.000000/8.000000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:n/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000,box=8.000000/0.000000/0.800000/false:8.000000/8.000000/15.200000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000,box=9.600000/-1.000000/1.000000/false:9.600000/15.000000/15.000000/0.000000/0.000000/22.500000/8.000000/8.000000/8.000000:w/1/0.000000/0.000000/16.000000/16.000000:e/2/0.000000/0.000000/16.000000/16.000000 @@ -1036,6 +1036,7 @@ patchblock:id=potted_dark_oak_sapling,patch0=FlowerPotTop,patch1=FlowerPotBottom patchblock:id=potted_fern,patch0=FlowerPotTop,patch1=FlowerPotBottom,patch2=FlowerPotSide,patch3=FlowerPotSide@90,patch4=FlowerPotSide@180,patch5=FlowerPotSide@270,patch6=FlowerPotDirt,patch7=FlowerPotFlower,patch8=FlowerPotFlower@90 # Flower pot with dandelion patchblock:id=potted_dandelion,patch0=FlowerPotTop,patch1=FlowerPotBottom,patch2=FlowerPotSide,patch3=FlowerPotSide@90,patch4=FlowerPotSide@180,patch5=FlowerPotSide@270,patch6=FlowerPotDirt,patch7=FlowerPotFlower,patch8=FlowerPotFlower@90 +patchblock:id=potted_golden_dandelion,patch0=FlowerPotTop,patch1=FlowerPotBottom,patch2=FlowerPotSide,patch3=FlowerPotSide@90,patch4=FlowerPotSide@180,patch5=FlowerPotSide@270,patch6=FlowerPotDirt,patch7=FlowerPotFlower,patch8=FlowerPotFlower@90 # Flower pot with poppy patchblock:id=potted_poppy,patch0=FlowerPotTop,patch1=FlowerPotBottom,patch2=FlowerPotSide,patch3=FlowerPotSide@90,patch4=FlowerPotSide@180,patch5=FlowerPotSide@270,patch6=FlowerPotDirt,patch7=FlowerPotFlower,patch8=FlowerPotFlower@90 # Flower pot with blue orchid @@ -1637,7 +1638,7 @@ patchblock:id=bubble_column [1.14-]patchblock:id=campfire,data=24,data=25,data=26,data=27 [1.14-]patchrotate:id=campfire,data=8,roty=90 # Campfire (unlit) (unlit log, lit log, fire) -[1-14-]modellist:id=campfire,data=12,data=13,data=14,data=15,box=1/0/0:5/4/16:n/0/0/4/4/8:e/0/0/1/16/5:s/0/0/4/4/8:w/0/16/0/0/4:u90/0/0/0/16/4:d90/0/0/0/16/4,box=0/3/11:16/7/15:n/0/16/0/0/4:e/0/0/4/4/8:s/0/0/0/16/4:w/0/0/4/4/8:u180/0/0/0/16/4:d/0/0/0/16/4,box=11/0/0:15/4/16:n/0/0/4/4/8:e/0/0/0/16/4:s/0/0/4/4/8:w/0/16/1/0/5:u90/0/0/0/16/4:d90/0/0/0/16/4,box=0/3/1:16/7/5:n/0/0/0/16/4:e/0/0/4/4/8:s/0/16/0/0/4:w/0/0/4/4/8:u180/0/0/0/16/4:d/0/0/0/16/4,box=5/0/0:11/1/16:n/0/0/15/6/16:s/0/10/15/16/16:u90/0/0/8/16/14:d90/0/0/8/16/14 +[1.14-]modellist:id=campfire,data=12,data=13,data=14,data=15,box=1/0/0:5/4/16:n/0/0/4/4/8:e/0/0/1/16/5:s/0/0/4/4/8:w/0/16/0/0/4:u90/0/0/0/16/4:d90/0/0/0/16/4,box=0/3/11:16/7/15:n/0/16/0/0/4:e/0/0/4/4/8:s/0/0/0/16/4:w/0/0/4/4/8:u180/0/0/0/16/4:d/0/0/0/16/4,box=11/0/0:15/4/16:n/0/0/4/4/8:e/0/0/0/16/4:s/0/0/4/4/8:w/0/16/1/0/5:u90/0/0/0/16/4:d90/0/0/0/16/4,box=0/3/1:16/7/5:n/0/0/0/16/4:e/0/0/4/4/8:s/0/16/0/0/4:w/0/0/4/4/8:u180/0/0/0/16/4:d/0/0/0/16/4,box=5/0/0:11/1/16:n/0/0/15/6/16:s/0/10/15/16/16:u90/0/0/8/16/14:d90/0/0/8/16/14 [1.14-]patchblock:id=campfire,data=4,data=5,data=6,data=7 [1.14-]patchrotate:id=campfire,data=12,roty=180 [1.14-]patchblock:id=campfire,data=20,data=21,data=22,data=23 @@ -1893,6 +1894,7 @@ patchblock:id=bubble_column [1.17-]boxblock:id=moss_carpet,ymax=0.0625 # Pointed dripstone [1.17-]patchblock:id=pointed_dripstone,patch0=VertX1Z0ToX0Z1,patch1=VertX1Z0ToX0Z1@90 +[26.2-]patchblock:id=sulfur_spike,patch0=VertX1Z0ToX0Z1,patch1=VertX1Z0ToX0Z1@90 # Candle [1.17-]modellist:id=%candle,state=candles:1/lit:true,box=7.000000/0.000000/7.000000:9.000000/6.000000/9.000000:n/0/0.000000/8.000000/2.000000/14.000000:e/0/0.000000/8.000000/2.000000/14.000000:w/0/0.000000/8.000000/2.000000/14.000000:d/0/0.000000/14.000000/2.000000/16.000000:u/0/0.000000/6.000000/2.000000/8.000000:s/0/0.000000/8.000000/2.000000/14.000000,box=7.500000/6.000000/8.000000:8.500000/7.000000/8.000000/0.000000/45.000000/0.000000/8.000000/6.000000/8.000000:n/0/0.000000/5.000000/1.000000/6.000000:s/0/0.000000/5.000000/1.000000/6.000000,box=7.500000/6.000000/8.000000:8.500000/7.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/6.000000/8.000000:n/0/0.000000/5.000000/1.000000/6.000000:s/0/0.000000/5.000000/1.000000/6.000000 [1.17-]modellist:id=%candle,state=candles:1/lit:false,box=7.000000/0.000000/7.000000:9.000000/6.000000/9.000000:n/0/0.000000/8.000000/2.000000/14.000000:e/0/0.000000/8.000000/2.000000/14.000000:w/0/0.000000/8.000000/2.000000/14.000000:d/0/0.000000/14.000000/2.000000/16.000000:u/0/0.000000/6.000000/2.000000/8.000000:s/0/0.000000/8.000000/2.000000/14.000000,box=7.500000/6.000000/8.000000:8.500000/7.000000/8.000000/0.000000/45.000000/0.000000/8.000000/6.000000/8.000000:n/0/0.000000/5.000000/1.000000/6.000000:s/0/0.000000/5.000000/1.000000/6.000000,box=7.500000/6.000000/8.000000:8.500000/7.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/6.000000/8.000000:n/0/0.000000/5.000000/1.000000/6.000000:s/0/0.000000/5.000000/1.000000/6.000000 @@ -3455,6 +3457,30 @@ modellist:id=%dropper,state=facing:down,box=0.000000/0.000000/0.000000:16.000000 [1.20.3-]modellist:id=%tuff_brick_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 [1.20.3-]modellist:id=%tuff_brick_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 [1.20.3-]customblock:id=%tuff_brick_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%sulfur_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%sulfur_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%sulfur_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%sulfur_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%sulfur_brick_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%sulfur_brick_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%sulfur_brick_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%sulfur_brick_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%polished_sulfur_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%polished_sulfur_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%polished_sulfur_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%polished_sulfur_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%cinnabar_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%cinnabar_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%cinnabar_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%cinnabar_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%cinnabar_brick_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%cinnabar_brick_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%cinnabar_brick_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%cinnabar_brick_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall +[26.2-]customblock:id=%polished_cinnabar_stairs,class=org.dynmap.hdmap.renderer.StairStateRenderer +[26.2-]modellist:id=%polished_cinnabar_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]modellist:id=%polished_cinnabar_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000 +[26.2-]customblock:id=%polished_cinnabar_wall,class=org.dynmap.hdmap.renderer.FenceWallBlockStateRenderer,type=tallwall [1.20.3-]customblock:id=%copper_door,id=%exposed_copper_door,id=%oxidized_copper_door,id=%weathered_copper_door,class=org.dynmap.hdmap.renderer.DoorStateRenderer [1.20.3-]customblock:id=%waxed_copper_door,id=%waxed_exposed_copper_door,id=%waxed_oxidized_copper_door,id=%waxed_weathered_copper_door,class=org.dynmap.hdmap.renderer.DoorStateRenderer diff --git a/DynmapCore/src/main/resources/texture_1.txt b/DynmapCore/src/main/resources/texture_1.txt index 730baa859..7133a7e7a 100644 --- a/DynmapCore/src/main/resources/texture_1.txt +++ b/DynmapCore/src/main/resources/texture_1.txt @@ -4547,6 +4547,26 @@ block:id=%melon_stem,patch0=0:melon_stem,blockcolor=foliagebiome,transparency=TR [1.20.3-]texture:id=exposed_copper_grate,filename=assets/minecraft/textures/block/exposed_copper_grate.png,xcount=1,ycount=1 [1.20.3-]texture:id=weathered_copper_grate,filename=assets/minecraft/textures/block/weathered_copper_grate.png,xcount=1,ycount=1 [1.20.3-]texture:id=oxidized_copper_grate,filename=assets/minecraft/textures/block/oxidized_copper_grate.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur,filename=assets/minecraft/textures/block/sulfur.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_bricks,filename=assets/minecraft/textures/block/sulfur_bricks.png,xcount=1,ycount=1 +[26.2-]texture:id=chiseled_sulfur,filename=assets/minecraft/textures/block/chiseled_sulfur.png,xcount=1,ycount=1 +[26.2-]texture:id=polished_sulfur,filename=assets/minecraft/textures/block/polished_sulfur.png,xcount=1,ycount=1 +[26.2-]texture:id=potent_sulfur,filename=assets/minecraft/textures/block/potent_sulfur.png,xcount=1,ycount=1 +[26.2-]texture:id=cinnabar,filename=assets/minecraft/textures/block/cinnabar.png,xcount=1,ycount=1 +[26.2-]texture:id=cinnabar_bricks,filename=assets/minecraft/textures/block/cinnabar_bricks.png,xcount=1,ycount=1 +[26.2-]texture:id=chiseled_cinnabar,filename=assets/minecraft/textures/block/chiseled_cinnabar.png,xcount=1,ycount=1 +[26.2-]texture:id=polished_cinnabar,filename=assets/minecraft/textures/block/polished_cinnabar.png,xcount=1,ycount=1 +[26.2-]texture:id=golden_dandelion,filename=assets/minecraft/textures/block/golden_dandelion.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_up_base,filename=assets/minecraft/textures/block/sulfur_spike_up_base.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_down_base,filename=assets/minecraft/textures/block/sulfur_spike_down_base.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_up_frustum,filename=assets/minecraft/textures/block/sulfur_spike_up_frustum.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_down_frustum,filename=assets/minecraft/textures/block/sulfur_spike_down_frustum.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_up_middle,filename=assets/minecraft/textures/block/sulfur_spike_up_middle.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_down_middle,filename=assets/minecraft/textures/block/sulfur_spike_down_middle.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_up_tip,filename=assets/minecraft/textures/block/sulfur_spike_up_tip.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_down_tip,filename=assets/minecraft/textures/block/sulfur_spike_down_tip.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_up_tip_merge,filename=assets/minecraft/textures/block/sulfur_spike_up_tip_merge.png,xcount=1,ycount=1 +[26.2-]texture:id=sulfur_spike_down_tip_merge,filename=assets/minecraft/textures/block/sulfur_spike_down_tip_merge.png,xcount=1,ycount=1 [1.20.3-]block:id=%suspicious_gravel,state=dusted:0,patch0=0:suspicious_gravel_0,patch1=0:suspicious_gravel_0,patch2=0:suspicious_gravel_0,patch3=0:suspicious_gravel_0,patch4=0:suspicious_gravel_0,patch5=0:suspicious_gravel_0,stdrot=true [1.20.3-]block:id=%suspicious_gravel,state=dusted:1,patch0=0:suspicious_gravel_1,patch1=0:suspicious_gravel_1,patch2=0:suspicious_gravel_1,patch3=0:suspicious_gravel_1,patch4=0:suspicious_gravel_1,patch5=0:suspicious_gravel_1,stdrot=true [1.20.3-]block:id=%suspicious_gravel,state=dusted:2,patch0=0:suspicious_gravel_2,patch1=0:suspicious_gravel_2,patch2=0:suspicious_gravel_2,patch3=0:suspicious_gravel_2,patch4=0:suspicious_gravel_2,patch5=0:suspicious_gravel_2,stdrot=true @@ -4571,6 +4591,61 @@ block:id=%melon_stem,patch0=0:melon_stem,blockcolor=foliagebiome,transparency=TR [1.20.3-]block:id=%tuff_brick_stairs,patch0-2=0:tuff_bricks,transparency=SEMITRANSPARENT,stdrot=true [1.20.3-]block:id=%tuff_brick_wall,patch0-2=0:tuff_bricks,transparency=SEMITRANSPARENT,stdrot=true [1.20.3-]block:id=%chiseled_tuff_bricks,patch0-5=0:chiseled_tuff_bricks,stdrot=true +[26.2-]block:id=%sulfur,patch0-5=0:sulfur,stdrot=true +[26.2-]block:id=%sulfur_slab,state=type:top,patch0=0:sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_slab,state=type:bottom,patch0=0:sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_slab,state=type:double,patch0-5=0:sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_stairs,patch0-2=0:sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_wall,patch0-2=0:sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_bricks,patch0-5=0:sulfur_bricks,stdrot=true +[26.2-]block:id=%sulfur_brick_slab,state=type:top,patch0=0:sulfur_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_brick_slab,state=type:bottom,patch0=0:sulfur_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_brick_slab,state=type:double,patch0-5=0:sulfur_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_brick_stairs,patch0-2=0:sulfur_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%sulfur_brick_wall,patch0-2=0:sulfur_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%chiseled_sulfur,patch0-5=0:chiseled_sulfur,stdrot=true +[26.2-]block:id=%polished_sulfur,patch0-5=0:polished_sulfur,stdrot=true +[26.2-]block:id=%polished_sulfur_slab,state=type:top,patch0=0:polished_sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_sulfur_slab,state=type:bottom,patch0=0:polished_sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_sulfur_slab,state=type:double,patch0-5=0:polished_sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_sulfur_stairs,patch0-2=0:polished_sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_sulfur_wall,patch0-2=0:polished_sulfur,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%potent_sulfur,patch0-5=0:potent_sulfur,stdrot=true +[26.2-]block:id=%cinnabar,patch0-5=0:cinnabar,stdrot=true +[26.2-]block:id=%cinnabar_slab,state=type:top,patch0=0:cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_slab,state=type:bottom,patch0=0:cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_slab,state=type:double,patch0-5=0:cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_stairs,patch0-2=0:cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_wall,patch0-2=0:cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_bricks,patch0-5=0:cinnabar_bricks,stdrot=true +[26.2-]block:id=%cinnabar_brick_slab,state=type:top,patch0=0:cinnabar_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_brick_slab,state=type:bottom,patch0=0:cinnabar_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_brick_slab,state=type:double,patch0-5=0:cinnabar_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_brick_stairs,patch0-2=0:cinnabar_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%cinnabar_brick_wall,patch0-2=0:cinnabar_bricks,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%chiseled_cinnabar,patch0-5=0:chiseled_cinnabar,stdrot=true +[26.2-]block:id=%polished_cinnabar,patch0-5=0:polished_cinnabar,stdrot=true +[26.2-]block:id=%polished_cinnabar_slab,state=type:top,patch0=0:polished_cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_cinnabar_slab,state=type:bottom,patch0=0:polished_cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_cinnabar_slab,state=type:double,patch0-5=0:polished_cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_cinnabar_stairs,patch0-2=0:polished_cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=%polished_cinnabar_wall,patch0-2=0:polished_cinnabar,transparency=SEMITRANSPARENT,stdrot=true +[26.2-]block:id=golden_dandelion,patch0-1=0:golden_dandelion,transparency=TRANSPARENT +[26.2-]block:id=potted_golden_dandelion,patch0-5=0:flower_pot,patch6=0:dirt,patch7-8=0:golden_dandelion,transparency=TRANSPARENT +[26.2-]block:id=%copper_golem_statue,id=%waxed_copper_golem_statue,allfaces=0:copper_block,stdrot=true +[26.2-]block:id=%exposed_copper_golem_statue,id=%waxed_exposed_copper_golem_statue,allfaces=0:exposed_copper,stdrot=true +[26.2-]block:id=%weathered_copper_golem_statue,id=%waxed_weathered_copper_golem_statue,allfaces=0:weathered_copper,stdrot=true +[26.2-]block:id=%oxidized_copper_golem_statue,id=%waxed_oxidized_copper_golem_statue,allfaces=0:oxidized_copper,stdrot=true +[26.2-]block:id=%sulfur_spike,state=thickness:base/vertical_direction:up,patch0-1=0:sulfur_spike_up_base,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:base/vertical_direction:down,patch0-1=0:sulfur_spike_down_base,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:frustum/vertical_direction:up,patch0-1=0:sulfur_spike_up_frustum,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:frustum/vertical_direction:down,patch0-1=0:sulfur_spike_down_frustum,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:middle/vertical_direction:up,patch0-1=0:sulfur_spike_up_middle,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:middle/vertical_direction:down,patch0-1=0:sulfur_spike_down_middle,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:tip/vertical_direction:up,patch0-1=0:sulfur_spike_up_tip,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:tip/vertical_direction:down,patch0-1=0:sulfur_spike_down_tip,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:tip_merge/vertical_direction:up,patch0-1=0:sulfur_spike_up_tip_merge,transparency=TRANSPARENT +[26.2-]block:id=%sulfur_spike,state=thickness:tip_merge/vertical_direction:down,patch0-1=0:sulfur_spike_down_tip_merge,transparency=TRANSPARENT [1.20.3-]block:id=%oxidized_chiseled_copper,patch0-5=0:oxidized_chiseled_copper,stdrot=true [1.20.3-]block:id=%weathered_chiseled_copper,patch0-5=0:weathered_chiseled_copper,stdrot=true [1.20.3-]block:id=%exposed_chiseled_copper,patch0-5=0:exposed_chiseled_copper,stdrot=true @@ -4984,12 +5059,25 @@ block:id=%melon_stem,patch0=0:melon_stem,blockcolor=foliagebiome,transparency=TR [1.21.4-]texture:id=creaking_heart_top,filename=assets/minecraft/textures/block/creaking_heart_top.png,xcount=1,ycount=1 [1.21.4-]texture:id=creaking_heart_active,filename=assets/minecraft/textures/block/creaking_heart_active.png,xcount=1,ycount=1 [1.21.4-]texture:id=creaking_heart,filename=assets/minecraft/textures/block/creaking_heart.png,xcount=1,ycount=1 +[26.2-]texture:id=creaking_heart_top_awake,filename=assets/minecraft/textures/block/creaking_heart_top_awake.png,xcount=1,ycount=1 +[26.2-]texture:id=creaking_heart_awake,filename=assets/minecraft/textures/block/creaking_heart_awake.png,xcount=1,ycount=1 +[26.2-]texture:id=creaking_heart_top_dormant,filename=assets/minecraft/textures/block/creaking_heart_top_dormant.png,xcount=1,ycount=1 +[26.2-]texture:id=creaking_heart_dormant,filename=assets/minecraft/textures/block/creaking_heart_dormant.png,xcount=1,ycount=1 [1.21.4-]block:id=%creaking_heart,state=axis:x/active:false,patch0=0:creaking_heart_top,patch1=6000:creaking_heart,patch2=6000:creaking_heart,patch3=0:creaking_heart_top,patch4=6000:creaking_heart,patch5=6000:creaking_heart,stdrot=true [1.21.4-]block:id=%creaking_heart,state=axis:y/active:false,patch0=0:creaking_heart,patch1=0:creaking_heart_top,patch2=0:creaking_heart,patch3=0:creaking_heart,patch4=0:creaking_heart_top,patch5=0:creaking_heart,stdrot=true [1.21.4-]block:id=%creaking_heart,state=axis:z/active:false,patch0=6000:creaking_heart,patch1=0:creaking_heart,patch2=0:creaking_heart_top,patch3=6000:creaking_heart,patch4=0:creaking_heart,patch5=0:creaking_heart_top,stdrot=true [1.21.4-]block:id=%creaking_heart,state=axis:x/active:true,patch0=0:creaking_heart_top_active,patch1=6000:creaking_heart_active,patch2=6000:creaking_heart_active,patch3=0:creaking_heart_top_active,patch4=6000:creaking_heart_active,patch5=6000:creaking_heart_active,stdrot=true [1.21.4-]block:id=%creaking_heart,state=axis:y/active:true,patch0=0:creaking_heart_active,patch1=0:creaking_heart_top_active,patch2=0:creaking_heart_active,patch3=0:creaking_heart_active,patch4=0:creaking_heart_top_active,patch5=0:creaking_heart_active,stdrot=true [1.21.4-]block:id=%creaking_heart,state=axis:z/active:true,patch0=6000:creaking_heart_active,patch1=0:creaking_heart_active,patch2=0:creaking_heart_top_active,patch3=6000:creaking_heart_active,patch4=0:creaking_heart_active,patch5=0:creaking_heart_top_active,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:x/creaking_heart_state:uprooted,patch0=0:creaking_heart_top,patch1=6000:creaking_heart,patch2=6000:creaking_heart,patch3=0:creaking_heart_top,patch4=6000:creaking_heart,patch5=6000:creaking_heart,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:y/creaking_heart_state:uprooted,patch0=0:creaking_heart,patch1=0:creaking_heart_top,patch2=0:creaking_heart,patch3=0:creaking_heart,patch4=0:creaking_heart_top,patch5=0:creaking_heart,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:z/creaking_heart_state:uprooted,patch0=6000:creaking_heart,patch1=0:creaking_heart,patch2=0:creaking_heart_top,patch3=6000:creaking_heart,patch4=0:creaking_heart,patch5=0:creaking_heart_top,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:x/creaking_heart_state:awake,patch0=0:creaking_heart_top_awake,patch1=6000:creaking_heart_awake,patch2=6000:creaking_heart_awake,patch3=0:creaking_heart_top_awake,patch4=6000:creaking_heart_awake,patch5=6000:creaking_heart_awake,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:y/creaking_heart_state:awake,patch0=0:creaking_heart_awake,patch1=0:creaking_heart_top_awake,patch2=0:creaking_heart_awake,patch3=0:creaking_heart_awake,patch4=0:creaking_heart_top_awake,patch5=0:creaking_heart_awake,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:z/creaking_heart_state:awake,patch0=6000:creaking_heart_awake,patch1=0:creaking_heart_awake,patch2=0:creaking_heart_top_awake,patch3=6000:creaking_heart_awake,patch4=0:creaking_heart_awake,patch5=0:creaking_heart_top_awake,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:x/creaking_heart_state:dormant,patch0=0:creaking_heart_top_dormant,patch1=6000:creaking_heart_dormant,patch2=6000:creaking_heart_dormant,patch3=0:creaking_heart_top_dormant,patch4=6000:creaking_heart_dormant,patch5=6000:creaking_heart_dormant,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:y/creaking_heart_state:dormant,patch0=0:creaking_heart_dormant,patch1=0:creaking_heart_top_dormant,patch2=0:creaking_heart_dormant,patch3=0:creaking_heart_dormant,patch4=0:creaking_heart_top_dormant,patch5=0:creaking_heart_dormant,stdrot=true +[26.2-]block:id=%creaking_heart,state=axis:z/creaking_heart_state:dormant,patch0=6000:creaking_heart_dormant,patch1=0:creaking_heart_dormant,patch2=0:creaking_heart_top_dormant,patch3=6000:creaking_heart_dormant,patch4=0:creaking_heart_dormant,patch5=0:creaking_heart_top_dormant,stdrot=true #All the resin stuff [1.21.4-]texture:id=resin_bricks,filename=assets/minecraft/textures/block/resin_bricks.png,xcount=1,ycount=1 [1.21.4-]texture:id=chiseled_resin_bricks,filename=assets/minecraft/textures/block/chiseled_resin_bricks.png,xcount=1,ycount=1 diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_cinnabar.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_cinnabar.png new file mode 100644 index 000000000..e3c3f0ce8 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_cinnabar.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_sulfur.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_sulfur.png new file mode 100644 index 000000000..1eadf2526 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/chiseled_sulfur.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar.png new file mode 100644 index 000000000..c983786a4 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar_bricks.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar_bricks.png new file mode 100644 index 000000000..4e242d993 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/cinnabar_bricks.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/golden_dandelion.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/golden_dandelion.png new file mode 100644 index 000000000..69b6aa292 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/golden_dandelion.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_cinnabar.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_cinnabar.png new file mode 100644 index 000000000..c198d8eef Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_cinnabar.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_sulfur.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_sulfur.png new file mode 100644 index 000000000..03a07cad9 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/polished_sulfur.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/potent_sulfur.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/potent_sulfur.png new file mode 100644 index 000000000..efafaff04 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/potent_sulfur.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur.png new file mode 100644 index 000000000..4edbb8c9f Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_bricks.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_bricks.png new file mode 100644 index 000000000..f37084854 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_bricks.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_base.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_base.png new file mode 100644 index 000000000..0b04a5316 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_base.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_frustum.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_frustum.png new file mode 100644 index 000000000..8d62485ae Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_frustum.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_middle.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_middle.png new file mode 100644 index 000000000..8ef4ac825 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_middle.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip.png new file mode 100644 index 000000000..53eaa22d7 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip_merge.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip_merge.png new file mode 100644 index 000000000..acd2b98e6 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_down_tip_merge.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_base.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_base.png new file mode 100644 index 000000000..0c793bb5d Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_base.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_frustum.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_frustum.png new file mode 100644 index 000000000..f669921e5 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_frustum.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_middle.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_middle.png new file mode 100644 index 000000000..d71213429 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_middle.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip.png new file mode 100644 index 000000000..637d8dd7c Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip_merge.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip_merge.png new file mode 100644 index 000000000..053a25335 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sulfur_spike_up_tip_merge.png differ diff --git a/DynmapCoreAPI/build.gradle b/DynmapCoreAPI/build.gradle index 93f716856..66d726888 100644 --- a/DynmapCoreAPI/build.gradle +++ b/DynmapCoreAPI/build.gradle @@ -6,7 +6,12 @@ eclipse { name = "Dynmap(DynmapCoreAPI)" } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. description = "DynmapCoreAPI" diff --git a/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar b/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar new file mode 100644 index 000000000..550b48b21 Binary files /dev/null and b/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar differ diff --git a/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar.MD5 b/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar.MD5 new file mode 100644 index 000000000..70bf91e24 --- /dev/null +++ b/Plugin/Dynmap-3.9-SNAPSHOT-fabric-26.2.jar.MD5 @@ -0,0 +1 @@ +643f0116823ca34c14886e1ddd1ea078 diff --git a/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar b/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar new file mode 100644 index 000000000..6088e2a63 Binary files /dev/null and b/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar differ diff --git a/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar.MD5 b/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar.MD5 new file mode 100644 index 000000000..49f8a7e5e --- /dev/null +++ b/Plugin/Dynmap-3.9-SNAPSHOT-spigot.jar.MD5 @@ -0,0 +1 @@ +f446201243e9a77475d9f0ebb714b30c diff --git a/Plugin/DynmapCore-3.9-SNAPSHOT.jar b/Plugin/DynmapCore-3.9-SNAPSHOT.jar new file mode 100644 index 000000000..204eb6948 Binary files /dev/null and b/Plugin/DynmapCore-3.9-SNAPSHOT.jar differ diff --git a/Plugin/DynmapCoreAPI-3.9-SNAPSHOT.jar b/Plugin/DynmapCoreAPI-3.9-SNAPSHOT.jar new file mode 100644 index 000000000..e96109f55 Binary files /dev/null and b/Plugin/DynmapCoreAPI-3.9-SNAPSHOT.jar differ diff --git a/README.md b/README.md index 640ac88b5..b3abf34ed 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,18 @@ To build and get all jars in `target/`, run: Or (on Windows): gradlew.bat setup build - -The Forge 1.12.2 versions (specifically ForgeGradle for these) are very sensitive to being built by JDK 8, so to build them, + +This main build covers Spigot/PaperMC, all `bukkit-helper-*`, and all `fabric-*` modules, and requires +Gradle 9.5.1 (handled automatically by the wrapper). Building `bukkit-helper-26-2` (Paper/Spigot 26.2 +support) additionally requires a **JDK 25** installation for Gradle's toolchain to pick up. + +Forge 1.14.4 through 1.21.11 build via ForgeGradle, which does not yet support Gradle 9, so they live in +their own build: + + cd forge-build + ./gradlew setup build + +The Forge 1.12.2 version (specifically ForgeGradle for this) is very sensitive to being built by JDK 8, so to build it, set JAVA_HOME to correspond to a JDK 8 installation, then build using the following; cd oldgradle @@ -46,10 +56,10 @@ The following target platforms are supported, and you can find them at the links | Server type | Version | Dynmap JAR | Where? | | ------------ | ------- | ---------- | ------ | -| Spigot/PaperMC | ≤1.21.4 | `Dynmap--spigot.jar` | [SpigotMC](https://www.spigotmc.org/resources/dynmap%C2%AE.274/) | -| Spigot/PaperMC | ≤1.21.4 | `Dynmap--spigot.jar` | [Modrinth](https://modrinth.com/plugin/dynmap/versions?l=paper&l=spigot) | +| Spigot/PaperMC | ≤1.21.11, 26.2 | `Dynmap--spigot.jar` | [SpigotMC](https://www.spigotmc.org/resources/dynmap%C2%AE.274/) | +| Spigot/PaperMC | ≤1.21.11, 26.2 | `Dynmap--spigot.jar` | [Modrinth](https://modrinth.com/plugin/dynmap/versions?l=paper&l=spigot) | | Forge | 1.12.2 - 1.20.6 | `Dynmap--forge-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | -| Fabric | 1.14.4 - 1.21.4 | `Dynmap--fabric-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | +| Fabric | 1.14.4 - 1.21.11, 26.2 | `Dynmap--fabric-.jar` | [Curseforge](https://www.curseforge.com/minecraft/mc-mods/dynmapforge) | # Data Storage Dynmap supports the following storage backends: diff --git a/build.gradle b/build.gradle index 662a53493..74ae2590c 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,8 @@ buildscript { plugins { //id "com.github.johnrengelman.shadow" version "7.1.0" - id "io.github.goooler.shadow" version "8.1.7" + //id "io.github.goooler.shadow" version "8.1.7" // Gradle-8-only; superseded by com.gradleup.shadow for Gradle 9 + id "com.gradleup.shadow" version "9.6.0" id 'java' id 'maven-publish' } @@ -52,13 +53,15 @@ ext { globals = new Globals() } -subprojects { - apply plugin: "io.github.goooler.shadow" +subprojects { + apply plugin: "com.gradleup.shadow" apply plugin: 'java' apply plugin: 'maven-publish' - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } diff --git a/bukkit-helper-113-2/build.gradle b/bukkit-helper-113-2/build.gradle index c15ea7a28..5801a6c19 100644 --- a/bukkit-helper-113-2/build.gradle +++ b/bukkit-helper-113-2/build.gradle @@ -8,7 +8,12 @@ eclipse { description = 'bukkit-helper-1.13.2' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-114-1/build.gradle b/bukkit-helper-114-1/build.gradle index 2b3f7f249..37494ae0c 100644 --- a/bukkit-helper-114-1/build.gradle +++ b/bukkit-helper-114-1/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.14.1' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-115/build.gradle b/bukkit-helper-115/build.gradle index 1cafc4a33..be85d260a 100644 --- a/bukkit-helper-115/build.gradle +++ b/bukkit-helper-115/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.15' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-116-2/build.gradle b/bukkit-helper-116-2/build.gradle index 8f6102507..772a17eca 100644 --- a/bukkit-helper-116-2/build.gradle +++ b/bukkit-helper-116-2/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.16.2' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-116-3/build.gradle b/bukkit-helper-116-3/build.gradle index 9a7dcf191..0c449d7e6 100644 --- a/bukkit-helper-116-3/build.gradle +++ b/bukkit-helper-116-3/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.16.3' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-116-4/build.gradle b/bukkit-helper-116-4/build.gradle index 6a0df89ba..007ffef6f 100644 --- a/bukkit-helper-116-4/build.gradle +++ b/bukkit-helper-116-4/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.16.4' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-116/build.gradle b/bukkit-helper-116/build.gradle index c3e81f899..1e5e7154d 100644 --- a/bukkit-helper-116/build.gradle +++ b/bukkit-helper-116/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.16' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-117/build.gradle b/bukkit-helper-117/build.gradle index fc862dffb..89fe041ff 100644 --- a/bukkit-helper-117/build.gradle +++ b/bukkit-helper-117/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.17' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(16) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('16') + targetCompatibility = JavaVersion.toVersion('16') +} +compileJava.sourceCompatibility = '16' +compileJava.targetCompatibility = '16' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-118-2/build.gradle b/bukkit-helper-118-2/build.gradle index cc86d1431..ea92796b6 100644 --- a/bukkit-helper-118-2/build.gradle +++ b/bukkit-helper-118-2/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.18.2' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-118/build.gradle b/bukkit-helper-118/build.gradle index f7093a23d..ff20b4dc7 100644 --- a/bukkit-helper-118/build.gradle +++ b/bukkit-helper-118/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.18' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-119-3/build.gradle b/bukkit-helper-119-3/build.gradle index 8a0525d12..305742796 100644 --- a/bukkit-helper-119-3/build.gradle +++ b/bukkit-helper-119-3/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.19.3' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-119-4/build.gradle b/bukkit-helper-119-4/build.gradle index 1f7a9179f..7e9d7fe2f 100644 --- a/bukkit-helper-119-4/build.gradle +++ b/bukkit-helper-119-4/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.19.4' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-119/build.gradle b/bukkit-helper-119/build.gradle index 4061ec3db..7df2c40a3 100644 --- a/bukkit-helper-119/build.gradle +++ b/bukkit-helper-119/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.19' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-120-2/build.gradle b/bukkit-helper-120-2/build.gradle index 6cca6f432..5b6a84e03 100644 --- a/bukkit-helper-120-2/build.gradle +++ b/bukkit-helper-120-2/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.20.2' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-120-4/build.gradle b/bukkit-helper-120-4/build.gradle index 33dc9cf40..79de551df 100644 --- a/bukkit-helper-120-4/build.gradle +++ b/bukkit-helper-120-4/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.20.4' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-120-5/build.gradle b/bukkit-helper-120-5/build.gradle index 15448e084..8906d3cd1 100644 --- a/bukkit-helper-120-5/build.gradle +++ b/bukkit-helper-120-5/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.20.5' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-120/build.gradle b/bukkit-helper-120/build.gradle index a32246715..1a4221fb9 100644 --- a/bukkit-helper-120/build.gradle +++ b/bukkit-helper-120/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.20' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-10/build.gradle b/bukkit-helper-121-10/build.gradle index 59fff7240..a9341aea7 100644 --- a/bukkit-helper-121-10/build.gradle +++ b/bukkit-helper-121-10/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.10' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-11/build.gradle b/bukkit-helper-121-11/build.gradle index 5a8ccc1f0..77d11fc84 100644 --- a/bukkit-helper-121-11/build.gradle +++ b/bukkit-helper-121-11/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.11' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-3/build.gradle b/bukkit-helper-121-3/build.gradle index 47b4464ac..6cec13eae 100644 --- a/bukkit-helper-121-3/build.gradle +++ b/bukkit-helper-121-3/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.3' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-4/build.gradle b/bukkit-helper-121-4/build.gradle index 863066193..19de57569 100644 --- a/bukkit-helper-121-4/build.gradle +++ b/bukkit-helper-121-4/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.4' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-5/build.gradle b/bukkit-helper-121-5/build.gradle index 0b5680747..8ddfc7b16 100644 --- a/bukkit-helper-121-5/build.gradle +++ b/bukkit-helper-121-5/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.5' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121-6/build.gradle b/bukkit-helper-121-6/build.gradle index e4aa9c39a..7c5a27c0e 100644 --- a/bukkit-helper-121-6/build.gradle +++ b/bukkit-helper-121-6/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21.7' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-121/build.gradle b/bukkit-helper-121/build.gradle index 6d8ecf422..e4130d1ec 100644 --- a/bukkit-helper-121/build.gradle +++ b/bukkit-helper-121/build.gradle @@ -6,7 +6,12 @@ eclipse { description = 'bukkit-helper-1.21' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. dependencies { implementation project(':bukkit-helper') diff --git a/bukkit-helper-26-2/.gitignore b/bukkit-helper-26-2/.gitignore new file mode 100644 index 000000000..84c048a73 --- /dev/null +++ b/bukkit-helper-26-2/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/bukkit-helper-26-2/build.gradle b/bukkit-helper-26-2/build.gradle new file mode 100644 index 000000000..8572c71eb --- /dev/null +++ b/bukkit-helper-26-2/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.21' +} + +eclipse { + project { + name = "Dynmap(Spigot-26.2)" + } +} + +description = 'bukkit-helper-26.2' + +// Paper 26.2 requires JDK 25 to compile against - use a real Gradle toolchain (rather than just a +// sourceCompatibility flag) so this module can be built with JDK 25 regardless of what JDK is +// running the Gradle daemon itself (root project still targets JDK 8 elsewhere). +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +repositories { + maven { url 'https://repo.papermc.io/repository/maven-public/' } +} + +dependencies { + implementation project(':bukkit-helper') + implementation project(':dynmap-api') + implementation project(path: ':DynmapCore', configuration: 'shadow') + // Minecraft ships unobfuscated as of 26.1+, so there is no more "spigot mappings" reobf jar + // (org.spigotmc:spigot / org.spigotmc:spigot-api) to depend on for NMS access - paperweight-userdev's + // dev bundle supplies the full compile classpath (Paper/Bukkit API + real Mojang-mapped net.minecraft.* + // classes) by itself, so no separate paper-api dependency is needed (or resolvable) here. + paperweight.paperDevBundle('26.2.build.+') +} diff --git a/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/BukkitVersionHelperSpigot26_2.java b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/BukkitVersionHelperSpigot26_2.java new file mode 100644 index 000000000..b5207289a --- /dev/null +++ b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/BukkitVersionHelperSpigot26_2.java @@ -0,0 +1,458 @@ +package org.dynmap.bukkit.helper.v26_2; + +import org.bukkit.*; +import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.BukkitMaterial; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.bukkit.helper.BukkitVersionHelperGeneric.TexturesPayload; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import net.minecraft.core.IdMapper; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.core.Registry; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.resources.Identifier; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * Helper for isolation of bukkit version specific issues + * + * NOTE: As of Minecraft 26.1+, Mojang ships the server unobfuscated, so there is no more + * "Spigot mappings" reobfuscated layer (the versioned org.bukkit.craftbukkit.vX_Y_RZ packages + * and single/double-letter obfuscated method names seen in earlier bukkit-helper-* modules). + * This class is written directly against the real (Mojang-mapped) net.minecraft.* names, + * derived from bukkit-helper-121-11's obfuscated calls using that module's own inline + * "// RealClass.realMethod" comments as the translation key. It has NOT yet been verified + * against a real compile of the paperweight 26.2 dev bundle - see CLAUDE.md / commit notes + * for current build status. + */ +public class BukkitVersionHelperSpigot26_2 extends BukkitVersionHelper { + + @Override + public boolean isUnsafeAsync() { + return false; + } + + /** + * Get block short name list + */ + @Override + public String[] getBlockNames() { + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + baseb = b; + continue; + } + Identifier id = BuiltInRegistries.BLOCK.getKey(b); + String bn = id.toString(); + if (bn != null) { + names.add(bn); + Log.info("block=" + bn); + } + } + return names.toArray(new String[0]); + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME); + } + return reg; + } + + private Object[] biomelist; + /** + * Get list of defined biomebase objects + */ + @Override + public Object[] getBiomeBaseList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + /** Get ID from biomebase */ + @Override + public int getBiomeBaseID(Object bb) { + return getBiomeReg().getId((Biome)bb); + } + + public static IdentityHashMap dataToState; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + @Override + public void initializeBlockStates() { + dataToState = new IdentityHashMap(); + HashMap lastBlockState = new HashMap(); + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + + // Loop through block data states + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bd = iter.next(); + Block b = bd.getBlock(); + Identifier id = BuiltInRegistries.BLOCK.getKey(b); + String bname = id.toString(); + DynmapBlockState lastbs = lastBlockState.get(bname); // See if we have seen this one + int idx = 0; + if (lastbs != null) { // Yes + idx = lastbs.getStateCount(); // Get number of states so far, since this is next + } + // Build state name + String sb = ""; + String fname = bd.toString(); + int off1 = fname.indexOf('['); + if (off1 >= 0) { + int off2 = fname.indexOf(']'); + sb = fname.substring(off1+1, off2); + } + int lightAtten = bd.getLightDampening(); + //Log.info("statename=" + bname + "[" + sb + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(lastbs).setStateIndex(idx).setBlockName(bname).setStateName(sb).setAttenuatesLight(lightAtten); + if (bd.isSolid()) { bld.setSolid(); } + if (bd.isAir()) { bld.setAir(); } + if (bd.is(BlockTags.OVERWORLD_NATURAL_LOGS)) { bld.setLog(); } + if (bd.is(BlockTags.LEAVES)) { bld.setLeaves(); } + if (!bd.getFluidState().isEmpty() && !(bd.getBlock() instanceof LiquidBlock)) { // Test if fluid type for block is not empty + bld.setWaterlogged(); + //Log.info("statename=" + bname + "[" + sb + "] = waterlogged"); + } + DynmapBlockState dbs = bld.build(); // Build state + + dataToState.put(bd, dbs); + lastBlockState.put(bname, (lastbs == null) ? dbs : lastbs); + Log.verboseinfo("blk=" + bname + ", idx=" + idx + ", state=" + sb + ", waterlogged=" + dbs.isWaterlogged()); + } + } + /** + * Create chunk cache for given chunks of given world + * @param dw - world + * @param chunks - chunk list + * @return cache + */ + @Override + public MapChunkCache getChunkCache(BukkitWorld dw, List chunks) { + MapChunkCache26_2 c = new MapChunkCache26_2(gencache); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + Biome biome = (Biome) bb; + return biome.getWaterColor(); + } + + /** Get temperature from biomebase */ + @Override + public float getBiomeBaseTemperature(Object bb) { + return ((Biome)bb).getBaseTemperature(); + } + + /** Get humidity from biomebase */ + @Override + public float getBiomeBaseHumidity(Object bb) { + String vals = ((Biome)bb).climateSettings.toString(); + float humidity = 0.5F; + int idx = vals.indexOf("downfall="); + if (idx >= 0) { + humidity = Float.parseFloat(vals.substring(idx+9, vals.indexOf(']', idx))); + } + return humidity; + } + + @Override + public Polygon getWorldBorder(World world) { + Polygon p = null; + WorldBorder wb = world.getWorldBorder(); + if (wb != null) { + Location c = wb.getCenter(); + double size = wb.getSize(); + if ((size > 1) && (size < 1E7)) { + size = size / 2; + p = new Polygon(); + p.addVertex(c.getX()-size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()+size); + p.addVertex(c.getX()-size, c.getZ()+size); + } + } + return p; + } + // Send title/subtitle to user + public void sendTitleText(Player p, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTIcks) { + if (p != null) { + p.sendTitle(title, subtitle, fadeInTicks, stayTicks, fadeOutTIcks); + } + } + + /** + * Get material map by block ID + */ + @Override + public BukkitMaterial[] getMaterialList() { + return new BukkitMaterial[4096]; // Not used + } + + @Override + public void unloadChunkNoSave(World w, Chunk c, int cx, int cz) { + Log.severe("unloadChunkNoSave not implemented"); + } + + private String[] biomenames; + @Override + public String[] getBiomeNames() { + if (biomenames == null) { + biomenames = new String[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomenames.length) { + biomenames = Arrays.copyOf(biomenames, bidx + biomenames.length); + } + biomenames[bidx] = b.toString(); + } + } + return biomenames; + } + + @Override + public String getStateStringByCombinedId(int blkid, int meta) { + Log.severe("getStateStringByCombinedId not implemented"); + return null; + } + @Override + /** Get ID string from biomebase */ + public String getBiomeBaseIDString(Object bb) { + return getBiomeReg().getKey((Biome)bb).getPath(); + } + @Override + public String getBiomeBaseResourceLocsation(Object bb) { + return getBiomeReg().getKey((Biome)bb).toString(); + } + + @Override + public Object getUnloadQueue(World world) { + Log.warning("getUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInUnloadQueue(Object unloadqueue, int x, int z) { + Log.warning("isInUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return false; + } + + @Override + public Object[] getBiomeBaseFromSnapshot(ChunkSnapshot css) { + Log.warning("getBiomeBaseFromSnapshot not implemented yet"); + // TODO Auto-generated method stub + return new Object[256]; + } + + @Override + public long getInhabitedTicks(Chunk c) { + return ((CraftChunk)c).getHandle(ChunkStatus.FULL).getInhabitedTime(); + } + + @Override + public Map getTileEntitiesForChunk(Chunk c) { + return ((CraftChunk)c).getHandle(ChunkStatus.FULL).blockEntities; + } + + @Override + public int getTileEntityX(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getX(); + } + + @Override + public int getTileEntityY(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getY(); + } + + @Override + public int getTileEntityZ(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getZ(); + } + + @Override + public Object readTileEntityNBT(Object te, World w) { + BlockEntity tileent = (BlockEntity) te; + CraftWorld cw = (CraftWorld) w; + return tileent.saveCustomOnly(cw.getHandle().registryAccess()); + } + + @Override + public Object getFieldValue(Object nbt, String field) { + CompoundTag rec = (CompoundTag) nbt; + Tag val = rec.get(field); + if(val == null) return null; + if(val instanceof ByteTag) { + return ((ByteTag)val).byteValue(); + } + else if(val instanceof ShortTag) { + return ((ShortTag)val).shortValue(); + } + else if(val instanceof IntTag) { + return ((IntTag)val).intValue(); + } + else if(val instanceof LongTag) { + return ((LongTag)val).longValue(); + } + else if(val instanceof FloatTag) { + return ((FloatTag)val).floatValue(); + } + else if(val instanceof DoubleTag) { + return ((DoubleTag)val).doubleValue(); + } + else if(val instanceof ByteArrayTag) { + return ((ByteArrayTag)val).getAsByteArray(); + } + else if(val instanceof StringTag) { + return ((StringTag)val).value(); + } + else if(val instanceof IntArrayTag) { + return ((IntArrayTag)val).getAsIntArray(); + } + return null; + } + + @Override + public Player[] getOnlinePlayers() { + Collection p = Bukkit.getServer().getOnlinePlayers(); + return p.toArray(new Player[0]); + } + + @Override + public double getHealth(Player p) { + return p.getHealth(); + } + + private static final Gson gson = new GsonBuilder().create(); + + /** + * Get skin URL for player + * @param player + */ + @Override + public String getSkinURL(Player player) { + String url = null; + CraftPlayer cp = (CraftPlayer)player; + GameProfile profile = cp.getProfile(); + if (profile != null) { + PropertyMap pm = profile.properties(); + if (pm != null) { + Collection txt = pm.get("textures"); + Property textureProperty = Iterables.getFirst(pm.get("textures"), null); + if (textureProperty != null) { + String val = textureProperty.value(); + if (val != null) { + TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(val), StandardCharsets.UTF_8); + result = gson.fromJson(json, TexturesPayload.class); + } catch (JsonParseException e) { + } catch (IllegalArgumentException x) { + Log.warning("Malformed response from skin URL check: " + val); + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } + } + return url; + } + // Get minY for world + @Override + public int getWorldMinY(World w) { + CraftWorld cw = (CraftWorld) w; + return cw.getMinHeight(); + } + @Override + public boolean useGenericCache() { + return true; + } + +} diff --git a/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/MapChunkCache26_2.java b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/MapChunkCache26_2.java new file mode 100644 index 000000000..2caf4deae --- /dev/null +++ b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/MapChunkCache26_2.java @@ -0,0 +1,111 @@ +package org.dynmap.bukkit.helper.v26_2; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.dynmap.DynmapChunk; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + * + * See the header note in BukkitVersionHelperSpigot26_2 - this is a direct translation of + * bukkit-helper-121-11's obfuscated (Spigot-mapped) calls to their real Mojang names, since + * Minecraft 26.1+ ships unobfuscated and there is no more reobfuscated "spigot" jar to target. + */ +public class MapChunkCache26_2 extends GenericMapChunkCache { + private World w; + /** + * Construct empty cache + */ + public MapChunkCache26_2(GenericChunkCache cc) { + super(cc); + } + + @Override + protected Supplier getLoadedChunkAsync(DynmapChunk chunk) { + CompletableFuture> chunkData = CompletableFuture.supplyAsync(() -> { + CraftWorld cw = (CraftWorld) w; + LevelChunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); + if (c == null) { + return Optional.empty(); + } + return Optional.of(SerializableChunkData.copyOf(cw.getHandle(), c)); + }, ((CraftServer) Bukkit.getServer()).getServer()); + return () -> chunkData.join().map(SerializableChunkData::write).map(NBT.NBTCompound::new).map(this::parseChunkFromNBT).orElse(null); + } + + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return null; + // LevelChunk.loaded is private with no public getter in 26.2 - getChunkIfLoaded() + // returning non-null already implies the chunk is loaded, so no extra check is needed. + LevelChunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); + if (c == null) return null; + SerializableChunkData chunkData = SerializableChunkData.copyOf(cw.getHandle(), c); + CompoundTag nbt = chunkData.write(); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; + } + + @Override + protected Supplier loadChunkAsync(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + CompletableFuture> genericChunk = cw.getHandle().getChunkSource().chunkMap.read(new ChunkPos(chunk.x, chunk.z)); + return () -> genericChunk.join().map(NBT.NBTCompound::new).map(this::parseChunkFromNBT).orElse(null); + } + + protected GenericChunk loadChunk(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + CompoundTag nbt = null; + ChunkPos cc = new ChunkPos(chunk.x, chunk.z); + GenericChunk gc = null; + try { // BUGBUG - convert this all to asyn properly, since now native async + nbt = cw.getHandle() + .getChunkSource() + .chunkMap + .read(cc) + .join().get(); + } catch (CancellationException cx) { + } catch (NoSuchElementException snex) { + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(BukkitWorld dw, List chunks) { + this.w = dw.getWorld(); + super.setChunks(dw, chunks); + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(Biome::getSpecialEffects).flatMap(BiomeSpecialEffects::foliageColorOverride).orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects fog = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); + if (fog == null) return colormap[bm.biomeLookup()]; + return fog.grassColorModifier().modifyColor(x, z, fog.grassColorOverride().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/NBT.java b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/NBT.java new file mode 100644 index 000000000..1cb79ad36 --- /dev/null +++ b/bukkit-helper-26-2/src/main/java/org/dynmap/bukkit/helper/v26_2/NBT.java @@ -0,0 +1,145 @@ +package org.dynmap.bukkit.helper.v26_2; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Optional; +import java.util.Set; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.keySet(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + // Like contains, but with an extra constraint on type + Tag base = obj.get(s); + if (base == null) + return false; + byte type = base.getId(); + if (type == i) + return true; + else if (i != TAG_ANY_NUMERIC) + return false; + return type == TAG_BYTE || type == TAG_SHORT || type == TAG_INT || type == TAG_LONG || type == TAG_FLOAT + || type == TAG_DOUBLE; + } + @Override + public byte getByte(String s) { + return obj.getByteOr(s, (byte)0); + } + @Override + public short getShort(String s) { + return obj.getShortOr(s, (short)0); + } + @Override + public int getInt(String s) { + return obj.getIntOr(s, 0); + } + @Override + public long getLong(String s) { + return obj.getLongOr(s, 0L); + } + @Override + public float getFloat(String s) { + return obj.getFloatOr(s, 0.0f); + } + @Override + public double getDouble(String s) { + return obj.getDoubleOr(s, 0.0); + } + @Override + public String getString(String s) { + return obj.getStringOr(s, ""); + } + @Override + public byte[] getByteArray(String s) { + Optional byteArr = obj.getByteArray(s); + return byteArr.orElseGet(() -> new byte[0]); + } + @Override + public int[] getIntArray(String s) { + Optional intArr = obj.getIntArray(s); + return intArr.orElseGet(() -> new int[0]); + } + @Override + public long[] getLongArray(String s) { + Optional longArr = obj.getLongArray(s); + return longArr.orElseGet(() -> new long[0]); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompoundOrEmpty(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + // i argument used to be used to constrain list type, but nbt lists no longer have types as of 1.21.5 + return new NBTList(obj.getListOrEmpty(s)); + } + @Override + public boolean getBoolean(String s) { + return getByte(s) != 0; + } + @Override + public String getAsString(String s) { + Tag t = obj.get(s); + return (t != null) ? t.asString().orElseGet(() -> "") : ""; + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + + public static class NBTList implements GenericNBTList { + private final ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getStringOr(idx, ""); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompoundOrEmpty(idx)); + } + public String toString() { + return obj.toString(); + } + } + + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/bukkit-helper/build.gradle b/bukkit-helper/build.gradle index 9263aef69..ac20c1b2d 100644 --- a/bukkit-helper/build.gradle +++ b/bukkit-helper/build.gradle @@ -8,7 +8,12 @@ eclipse { description = 'bukkit-helper' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation project(':dynmap-api') diff --git a/dynmap-api/build.gradle b/dynmap-api/build.gradle index fe9231737..ac1491cd2 100644 --- a/dynmap-api/build.gradle +++ b/dynmap-api/build.gradle @@ -8,7 +8,12 @@ eclipse { description = "dynmap-api" -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { compileOnly group: 'org.bukkit', name: 'bukkit', version:'1.7.10-R0.1-SNAPSHOT' diff --git a/fabric-1.14.4/build.gradle b/fabric-1.14.4/build.gradle index d2c9b13e9..8e5e58d2c 100644 --- a/fabric-1.14.4/build.gradle +++ b/fabric-1.14.4/build.gradle @@ -10,7 +10,7 @@ eclipse { } } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -51,11 +51,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.15.2/build.gradle b/fabric-1.15.2/build.gradle index c3b32a161..eb9f23474 100644 --- a/fabric-1.15.2/build.gradle +++ b/fabric-1.15.2/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -49,11 +49,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.16.4/build.gradle b/fabric-1.16.4/build.gradle index e3a80ca46..ff0f326e5 100644 --- a/fabric-1.16.4/build.gradle +++ b/fabric-1.16.4/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -49,11 +49,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.17.1/build.gradle b/fabric-1.17.1/build.gradle index db7e77641..a20718e38 100644 --- a/fabric-1.17.1/build.gradle +++ b/fabric-1.17.1/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -49,11 +49,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.18.2/build.gradle b/fabric-1.18.2/build.gradle index 48fada538..8a187334a 100644 --- a/fabric-1.18.2/build.gradle +++ b/fabric-1.18.2/build.gradle @@ -3,7 +3,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -13,7 +13,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. configurations { shadow @@ -60,11 +65,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.19.4/build.gradle b/fabric-1.19.4/build.gradle index b3e71012a..27e963e16 100644 --- a/fabric-1.19.4/build.gradle +++ b/fabric-1.19.4/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -12,7 +12,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. configurations { shadow @@ -63,11 +68,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.20.6/build.gradle b/fabric-1.20.6/build.gradle index da0a1d0e2..3b86a5e55 100644 --- a/fabric-1.20.6/build.gradle +++ b/fabric-1.20.6/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -12,7 +12,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. configurations { shadow @@ -63,11 +68,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.21.11/build.gradle b/fabric-1.21.11/build.gradle index 06e02c7a0..90f7c6f93 100644 --- a/fabric-1.21.11/build.gradle +++ b/fabric-1.21.11/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -12,7 +12,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. configurations { shadow @@ -63,11 +68,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.21.6/build.gradle b/fabric-1.21.6/build.gradle index bce1c28d2..eaa512f4a 100644 --- a/fabric-1.21.6/build.gradle +++ b/fabric-1.21.6/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -12,7 +12,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. configurations { shadow @@ -63,11 +68,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-1.21.9-10/build.gradle b/fabric-1.21.9-10/build.gradle index 8a8de3289..737439ac3 100644 --- a/fabric-1.21.9-10/build.gradle +++ b/fabric-1.21.9-10/build.gradle @@ -2,7 +2,7 @@ plugins { id 'fabric-loom' version '1.13.6' } -archivesBaseName = "Dynmap" +base.archivesName = "Dynmap" version = parent.version group = parent.group @@ -12,7 +12,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. configurations { shadow @@ -63,11 +68,11 @@ jar { } remapJar { - archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}-10.jar" // TODO: Remove -10 when updating after 1.21.10 release + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}-10.jar" // TODO: Remove -10 when updating after 1.21.10 release destinationDirectory = file '../target' } remapJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } diff --git a/fabric-26.2/.gitignore b/fabric-26.2/.gitignore new file mode 100644 index 000000000..8b87af68e --- /dev/null +++ b/fabric-26.2/.gitignore @@ -0,0 +1,32 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse + +*.launch + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# fabric + +run/ + +# other +*.log diff --git a/fabric-26.2/build.gradle b/fabric-26.2/build.gradle new file mode 100644 index 000000000..28a933051 --- /dev/null +++ b/fabric-26.2/build.gradle @@ -0,0 +1,84 @@ +plugins { + id 'net.fabricmc.fabric-loom' version '1.17.16' +} + +base.archivesName = "Dynmap" +version = parent.version +group = parent.group + +eclipse { + project { + name = "Dynmap(Fabric-26.2)" + } +} + +// Minecraft 26.1+ ships unobfuscated, so Loom no longer remaps anything - mods compile and run +// directly against the real (Mojang-named) classes. This means: no "mappings" dependency, no +// "mod*" dependency configurations (they're just normal implementation/compileOnly now), and no +// remapJar task (the plain jar task IS the final artifact). +tasks.withType(JavaCompile).configureEach { + it.options.release = 25 +} + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 +} + +configurations { + shadow + implementation.extendsFrom(shadow) +} + +repositories { + mavenCentral() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + implementation "net.fabricmc:fabric-loader:${project.loader_version}" + implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' + + shadow project(path: ':DynmapCore', configuration: 'shadow') + + compileOnly "me.lucko:fabric-permissions-api:0.7.0" + compileOnly 'net.luckperms:api:5.4' +} + +processResources { + filesMatching('fabric.mod.json') { + expand "version": project.version + } +} + +java { + // Loom will automatically attach sourcesJar to the "build" task if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + archiveFileName = "${base.archivesName.get()}-${project.version}-fabric-${project.minecraft_version}.jar" + destinationDirectory = file '../target' + from "LICENSE" + from { + configurations.shadow.collect { it.toString().contains("guava") ? null : it.isDirectory() ? it : zipTree(it) } + } +} + +jar.doLast { + task -> + ant.checksum file: task.archiveFile.get().asFile +} + +// This module bundles DynmapCore manually above via the custom "shadow" configuration + jar.from(zipTree(...)). +// The com.gradleup.shadow PLUGIN (applied to every subproject at the root level) also auto-registers its own +// unrelated "shadowJar" task that bundles the whole Gradle runtimeClasspath by default - with plain +// `implementation` deps on fabric-api/fabric-loader/minecraft (the new no-remap convention) that classpath is +// huge and blows past the 65535-entry zip limit. Disable it since it's not what builds this module's jar. +tasks.named('shadowJar') { + enabled = false +} diff --git a/fabric-26.2/gradle.properties b/fabric-26.2/gradle.properties new file mode 100644 index 000000000..b994ecaa9 --- /dev/null +++ b/fabric-26.2/gradle.properties @@ -0,0 +1,6 @@ +minecraft_version=26.2 + +# As of Minecraft 26.1+, Mojang ships the server unobfuscated and Yarn mappings were +# discontinued - use official Mojang mappings via Loom instead (see build.gradle). +loader_version=0.19.3 +fabric_version=0.155.2+26.2 diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapMod.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapMod.java new file mode 100644 index 000000000..f85ff2511 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapMod.java @@ -0,0 +1,50 @@ +package org.dynmap.fabric_26_2; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class DynmapMod implements ModInitializer { + private static final String MODID = "dynmap"; + private static final ModContainer MOD_CONTAINER = FabricLoader.getInstance().getModContainer(MODID) + .orElseThrow(() -> new RuntimeException("Failed to get mod container: " + MODID)); + // The instance of your mod that Fabric uses. + public static DynmapMod instance; + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + @Override + public void onInitialize() { + instance = this; + + Path path = MOD_CONTAINER.getRootPath(); + try { + jarfile = new File(DynmapCore.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + } catch (URISyntaxException e) { + Log.severe("Unable to get DynmapCore jar path", e); + } + + if (path.getFileSystem().provider().getScheme().equals("jar")) { + path = Paths.get(path.getFileSystem().toString()); + jarfile = path.toFile(); + } + + ver = MOD_CONTAINER.getMetadata().getVersion().getFriendlyString(); + + Log.setLogger(new FabricLogger()); + org.dynmap.modsupport.ModSupportImpl.init(); + + // Initialize the plugin, we will enable it fully when the server starts. + plugin = new DynmapPlugin(); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapPlugin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapPlugin.java new file mode 100644 index 000000000..7d208fb9b --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/DynmapPlugin.java @@ -0,0 +1,807 @@ +package org.dynmap.fabric_26_2; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.tags.BlockTags; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.Registry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.resources.Identifier; +import net.minecraft.core.IdMapper; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import org.dynmap.*; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapCommandSender; +import org.dynmap.common.DynmapListenerManager; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.fabric_26_2.command.DmapCommand; +import org.dynmap.fabric_26_2.command.DmarkerCommand; +import org.dynmap.fabric_26_2.command.DynmapCommand; +import org.dynmap.fabric_26_2.command.DynmapExpCommand; +import org.dynmap.fabric_26_2.event.BlockEvents; +import org.dynmap.fabric_26_2.event.CustomServerLifecycleEvents; +import org.dynmap.fabric_26_2.event.PlayerEvents; +import org.dynmap.fabric_26_2.permissions.*; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; + +import java.io.File; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.regex.Pattern; + + +public class DynmapPlugin { + // FIXME: Fix package-private fields after splitting is done + DynmapCore core; + private PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + MapManager mapManager; + /** + * Server is set when running and unset at shutdown. + */ + private net.minecraft.server.MinecraftServer server; + public static DynmapPlugin plugin; + ChatHandler chathandler; + private HashMap sortWeights = new HashMap(); + private HashMap worlds = new HashMap(); + private LevelAccessor last_world; + private FabricWorld last_fworld; + private Map players = new HashMap(); + private FabricServer fserver; + private boolean tickregistered = false; + // TPS calculator + double tps; + long lasttick; + long avgticklen; + // Per tick limit, in nsec + long perTickLimit = (50000000); // 50 ms + private boolean useSaveFolder = true; + + private static final String[] TRIGGER_DEFAULTS = {"blockupdate", "chunkpopulate", "chunkgenerate"}; + + static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + DynmapPlugin() { + plugin = this; + // Fabric events persist between server instances + ServerLifecycleEvents.SERVER_STARTING.register(this::serverStart); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> registerCommands(dispatcher)); + CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.register(this::serverStarted); + ServerLifecycleEvents.SERVER_STOPPING.register(this::serverStop); + } + + int getSortWeight(String name) { + return sortWeights.getOrDefault(name, 0); + } + + void setSortWeight(String name, int wt) { + sortWeights.put(name, wt); + } + + void dropSortWeight(String name) { + sortWeights.remove(name); + } + + public static class BlockUpdateRec { + LevelAccessor w; + String wid; + int x, y, z; + } + + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + public void initializeBlockStates() { + stateByID = new DynmapBlockState[512 * 32]; // Simple map - scale as needed + Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getId(bs); + if (idx >= stateByID.length) { + int plen = stateByID.length; + stateByID = Arrays.copyOf(stateByID, idx*11/10); // grow array by 10% + Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); + } + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + basebs = null; + baseidx = idx; + baseb = b; + } + + Identifier ui = BuiltInRegistries.BLOCK.getKey(b); + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = bs.getLightDampening(); + //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (bs.getSoundType() != null) { bld.setMaterial(bs.getSoundType().toString()); } + if (bs.isSolid()) { bld.setSolid(); } + if (bs.isAir()) { bld.setAir(); } + if (bs.is(BlockTags.LOGS)) { bld.setLog(); } + if (bs.is(BlockTags.LEAVES)) { bld.setLeaves(); } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { basebs = dbs; } + } + } + } + + public static final Item getItemByID(int id) { + return Item.byId(id); + } + + FabricPlayer getOrAddPlayer(ServerPlayer player) { + String name = player.getName().getString(); + FabricPlayer fp = players.get(name); + if (fp != null) { + fp.player = player; + } else { + fp = new FabricPlayer(this, player); + players.put(name, fp); + } + return fp; + } + + static class ChatMessage { + String message; + ServerPlayer sender; + } + + ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + + public static class ChatHandler { + private final DynmapPlugin plugin; + + ChatHandler(DynmapPlugin plugin) { + this.plugin = plugin; + } + + public void handleChat(ServerPlayer player, String message) { + if (!message.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = message; + cm.sender = player; + plugin.msgqueue.add(cm); + } + } + } + + public FabricServer getFabricServer() { + return fserver; + } + + private void serverStart(MinecraftServer server) { + // Set the server so we don't NPE during setup + this.server = server; + this.fserver = new FabricServer(this, server); + this.onEnable(); + } + + private void serverStarted(MinecraftServer server) { + this.onStart(); + if (core != null) { + core.serverStarted(); + } + } + + private void serverStop(MinecraftServer server) { + this.onDisable(); + this.server = null; + } + + public boolean isOp(String player) { + String[] ops = server.getPlayerList().getOps().getUserList(); + + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + + // TODO: Consider whether cheats are enabled for integrated server + return server.isSingleplayer() && server.isSingleplayerOwner(new net.minecraft.server.players.NameAndId(server.getPlayerList().getPlayerByName(player).getGameProfile())); + } + + boolean hasPerm(Player psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((ph != null) && (psender != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + boolean hasPermNode(Player psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((ph != null) && (psender != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { + return true; + } + return permissions.hasPermissionNode(psender, permission); + } + + Set hasOfflinePermissions(String player, Set perms) { + Set rslt = null; + PermissionsHandler ph = PermissionsHandler.getHandler(); + if (ph != null) { + rslt = ph.hasOfflinePermissions(player, perms); + } + Set rslt2 = hasOfflinePermissions(player, perms); + if ((rslt != null) && (rslt2 != null)) { + Set newrslt = new HashSet(rslt); + newrslt.addAll(rslt2); + rslt = newrslt; + } else if (rslt2 != null) { + rslt = rslt2; + } + return rslt; + } + + boolean hasOfflinePermission(String player, String perm) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if (ph != null) { + if (ph.hasOfflinePermission(player, perm)) { + return true; + } + } + return permissions.hasOfflinePermission(player, perm); + } + + void setChatHandler(ChatHandler chatHandler) { + plugin.chathandler = chatHandler; + } + + public class TexturesPayload { + public long timestamp; + public String profileId; + public String profileName; + public boolean isPublic; + public Map textures; + + } + + public class ProfileTexture { + public String url; + } + + // Biome.climateSettings is a private field with no public getter in vanilla (unlike some + // patched server jars), and downfall isn't exposed anywhere else on Biome - fall back to + // reflection to read it. + private static java.lang.reflect.Field climateSettingsField; + private static float getBiomeDownfall(Biome bb) { + try { + if (climateSettingsField == null) { + climateSettingsField = Biome.class.getDeclaredField("climateSettings"); + climateSettingsField.setAccessible(true); + } + Object climateSettings = climateSettingsField.get(bb); + return (float) climateSettings.getClass().getMethod("downfall").invoke(climateSettings); + } catch (ReflectiveOperationException x) { + Log.severe("Unable to read biome downfall", x); + return 0.5F; + } + } + + public void loadExtraBiomes(String mcver) { + int cnt = 0; + BiomeMap.loadWellKnownByVersion(mcver); + + Registry biomeRegistry = getFabricServer().getBiomeRegistry(); + Biome[] list = getFabricServer().getBiomeList(biomeRegistry); + + for (int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + String id = biomeRegistry.getKey(bb).getPath(); + String rl = biomeRegistry.getKey(bb).toString(); + float tmp = bb.getBaseTemperature(), hum = getBiomeDownfall(bb); + int watermult = bb.getWaterColor(); + Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } + else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + Log.verboseinfo("Add custom biome [" + bmap.toString() + "] (" + i + ")"); + cnt++; + } + else { + bmap.setTemperature(tmp); + bmap.setRainfall(hum); + } + if (watermult != -1) { + bmap.setWaterColorMultiplier(watermult); + Log.verboseinfo("Set watercolormult for " + bmap.toString() + " (" + i + ") to " + Integer.toHexString(watermult)); + } + bmap.setBiomeObject(bb); + } + } + if (cnt > 0) + Log.info("Added " + cnt + " custom biome mappings"); + } + + private String[] getBiomeNames() { + Registry biomeRegistry = getFabricServer().getBiomeRegistry(); + Biome[] list = getFabricServer().getBiomeList(biomeRegistry); + String[] lst = new String[list.length]; + for (int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + lst[i] = biomeRegistry.getKey(bb).getPath(); + } + } + return lst; + } + + public void onEnable() { + /* Get MC version */ + String mcver = server.getServerVersion(); + + /* Load extra biomes */ + loadExtraBiomes(mcver); + /* Set up player login/quit event handler */ + registerPlayerLoginListener(); + + /* Initialize permissions handler */ + if (FabricLoader.getInstance().isModLoaded("luckperms")) { + Log.info("Using luckperms for access control"); + permissions = new LuckPermissions(); + } + else if (FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0")) { + Log.info("Using fabric-permissions-api for access control"); + permissions = new FabricPermissions(); + } else { + /* Initialize permissions handler */ + permissions = FilePermissions.create(); + if (permissions == null) { + permissions = new OpPermissions(new String[]{"webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self"}); + } + } + /* Get and initialize data folder */ + File dataDirectory = new File("dynmap"); + + if (!dataDirectory.exists()) { + dataDirectory.mkdirs(); + } + + /* Instantiate core */ + if (core == null) { + core = new DynmapCore(); + } + + /* Inject dependencies */ + core.setPluginJarFile(DynmapMod.jarfile); + core.setPluginVersion(DynmapMod.ver); + core.setMinecraftVersion(mcver); + core.setDataFolder(dataDirectory); + core.setServer(fserver); + core.setTriggerDefault(TRIGGER_DEFAULTS); + core.setBiomeNames(getBiomeNames()); + + if (!core.initConfiguration(null)) { + return; + } + // Extract default permission example, if needed + File filepermexample = new File(core.getDataFolder(), "permissions.yml.example"); + core.createDefaultFileFromResource("/permissions.yml.example", filepermexample); + + DynmapCommonAPIListener.apiInitialized(core); + } + + private DynmapCommand dynmapCmd; + private DmapCommand dmapCmd; + private DmarkerCommand dmarkerCmd; + private DynmapExpCommand dynmapexpCmd; + + public void registerCommands(CommandDispatcher cd) { + dynmapCmd = new DynmapCommand(this); + dmapCmd = new DmapCommand(this); + dmarkerCmd = new DmarkerCommand(this); + dynmapexpCmd = new DynmapExpCommand(this); + dynmapCmd.register(cd); + dmapCmd.register(cd); + dmarkerCmd.register(cd); + dynmapexpCmd.register(cd); + + Log.info("Register commands"); + } + + public void onStart() { + initializeBlockStates(); + /* Enable core */ + if (!core.enableCore(null)) { + return; + } + core_enabled = true; + VersionCheck.runCheck(core); + // Get per tick time limit + perTickLimit = core.getMaxTickUseMS() * 1000000; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + + /* Register tick handler */ + if (!tickregistered) { + ServerTickEvents.END_SERVER_TICK.register(server -> fserver.tickEvent(server)); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + for (FabricWorld w : worlds.values()) { + if (core.processWorldLoad(w)) { /* Have core process load first - fire event listeners if good load after */ + if (w.isLoaded()) { + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_LOAD, w); + } + } + } + core.updateConfigHashcode(); + + /* Register our update trigger events */ + registerEvents(); + Log.info("Register events"); + + //DynmapCommonAPIListener.apiInitialized(core); + + Log.info("Enabled"); + } + + public void onDisable() { + DynmapCommonAPIListener.apiTerminated(); + + //if (metrics != null) { + // metrics.stop(); + // metrics = null; + //} + /* Save worlds */ + saveWorlds(); + + /* Purge tick queue */ + fserver.clearTaskQueue(); + + /* Disable core */ + core.disableCore(); + core_enabled = false; + + if (sscache != null) { + sscache.cleanup(); + sscache = null; + } + + Log.info("Disabled"); + } + + // TODO: Clean a bit + public void handleCommand(CommandSourceStack commandSource, String cmd, String[] args) throws CommandSyntaxException { + DynmapCommandSender dsender; + ServerPlayer psender = null; + + // getPlayer throws a CommandSyntaxException, so getEntity and instanceof for safety + if (commandSource.getEntity() instanceof ServerPlayer) { + psender = commandSource.getPlayerOrException(); + } + + if (psender != null) { + // FIXME: New Player? Why not query the current player list. + dsender = new FabricPlayer(this, psender); + } else { + dsender = new FabricCommandSender(commandSource); + } + + core.processCommand(dsender, cmd, cmd, args); + } + + public class PlayerTracker { + public void onPlayerLogin(ServerPlayer player) { + if (!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer(player); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(DynmapListenerManager.EventType.PLAYER_JOIN, dp); + } + }, 2); + } + + public void onPlayerLogout(ServerPlayer player) { + if (!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer(player); + final String name = player.getName().getString(); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(DynmapListenerManager.EventType.PLAYER_QUIT, dp); + players.remove(name); + } + }, 0); + } + + public void onPlayerChangedDimension(ServerPlayer player) { + if (!core_enabled) return; + getOrAddPlayer(player); // Freshen player object reference + } + + public void onPlayerRespawn(ServerPlayer player) { + if (!core_enabled) return; + getOrAddPlayer(player); // Freshen player object reference + } + } + + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + PlayerEvents.PLAYER_LOGGED_IN.register(player -> playerTracker.onPlayerLogin(player)); + PlayerEvents.PLAYER_LOGGED_OUT.register(player -> playerTracker.onPlayerLogout(player)); + PlayerEvents.PLAYER_CHANGED_DIMENSION.register(player -> playerTracker.onPlayerChangedDimension(player)); + PlayerEvents.PLAYER_RESPAWN.register(player -> playerTracker.onPlayerRespawn(player)); + } + } + + public class WorldTracker { + public void handleWorldLoad(MinecraftServer server, ServerLevel world) { + if (!core_enabled) return; + + final FabricWorld fw = getWorld(world); + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + if (core.processWorldLoad(fw)) // Have core process load first - fire event listeners if good load after + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_LOAD, fw); + } + }, 0); + } + + public void handleWorldUnload(MinecraftServer server, ServerLevel world) { + if (!core_enabled) return; + + final FabricWorld fw = getWorld(world); + if (fw != null) { + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_UNLOAD, fw); + core.processWorldUnload(fw); + } + }, 0); + // Set world unloaded (needs to be immediate, since it may be invalid after event) + fw.setWorldUnloaded(); + // Clean up tracker + //WorldUpdateTracker wut = updateTrackers.remove(fw.getName()); + //if(wut != null) wut.world = null; + } + } + + public void handleChunkGenerate(ServerLevel world, net.minecraft.world.level.chunk.LevelChunk chunk) { + if (!onchunkgenerate) return; + + FabricWorld fw = getWorld(world, false); + ChunkPos chunkPos = chunk.getPos(); + + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = chunk.getSections(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (!sections[i].hasOnlyAir())) { + int sy = chunk.getMinY() + i * 16; // Sections are always 16 blocks tall + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + if (ymax != Integer.MIN_VALUE) { + mapManager.touchVolume(fw.getName(), + chunkPos.getMinBlockX(), ymin, chunkPos.getMinBlockZ(), + chunkPos.getMaxBlockX(), ymax, chunkPos.getMaxBlockZ(), + "chunkgenerate"); + } + } + + public void handleBlockEvent(Level world, BlockPos pos) { + if (!core_enabled) return; + if (!onblockchange) return; + if (!(world instanceof ServerLevel)) return; + + BlockUpdateRec r = new BlockUpdateRec(); + r.w = world; + FabricWorld fw = getWorld(world, false); + if (fw == null) return; + r.wid = fw.getName(); + r.x = pos.getX(); + r.y = pos.getY(); + r.z = pos.getZ(); + blockupdatequeue.add(r); + } + } + + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + boolean onblockchange_with_id = false; + + private void registerEvents() { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if (onblockchange_with_id) + onblockchange = true; + if (worldTracker == null) + worldTracker = new WorldTracker(); + if (onchunkpopulate || onchunkgenerate) { + ServerChunkEvents.CHUNK_GENERATE.register((world, chunk) -> worldTracker.handleChunkGenerate(world, chunk)); + } + if (onblockchange) { + BlockEvents.BLOCK_EVENT.register((world, pos) -> worldTracker.handleBlockEvent(world, pos)); + } + + ServerLevelEvents.LOAD.register((server, world) -> worldTracker.handleWorldLoad(server, world)); + ServerLevelEvents.UNLOAD.register((server, world) -> worldTracker.handleWorldUnload(server, world)); + } + + FabricWorld getWorldByName(String name) { + return worlds.get(name); + } + + FabricWorld getWorld(Level w) { + return getWorld(w, true); + } + + private FabricWorld getWorld(Level w, boolean add_if_not_found) { + if (last_world == w) { + return last_fworld; + } + String wname = FabricWorld.getWorldName(this, w); + + for (FabricWorld fw : worlds.values()) { + if (fw.getRawName().equals(wname)) { + last_world = w; + last_fworld = fw; + if (!fw.isLoaded()) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + FabricWorld fw = null; + if (add_if_not_found) { + /* Add to list if not found */ + fw = new FabricWorld(this, w); + worlds.put(fw.getName(), fw); + } + last_world = w; + last_fworld = fw; + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE); + ConfigurationNode cn = new ConfigurationNode(f); + ArrayList> lst = new ArrayList>(); + for (DynmapWorld fw : core.mapManager.getWorlds()) { + HashMap vals = new HashMap(); + vals.put("name", fw.getRawName()); + vals.put("height", fw.worldheight); + vals.put("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((FabricWorld) fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", FabricWorld.getMaxWorldHeight()); + + cn.save(); + } + + private void loadWorlds() { + File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE); + if (f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + FabricWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if (lst == null) { + Log.warning(String.format("Discarding bad %s", FabricWorld.SAVED_WORLDS_FILE)); + return; + } + + for (Map world : lst) { + try { + String name = (String) world.get("name"); + int height = (Integer) world.get("height"); + Integer miny = (Integer) world.get("miny"); + int sealevel = (Integer) world.get("sealevel"); + boolean nether = (Boolean) world.get("nether"); + boolean theend = (Boolean) world.get("the_end"); + String title = (String) world.get("title"); + if (name != null) { + FabricWorld fw = new FabricWorld(this, name, height, sealevel, nether, theend, title, (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning(String.format("Unable to load saved worlds from %s", FabricWorld.SAVED_WORLDS_FILE)); + return; + } + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricAdapter.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricAdapter.java new file mode 100644 index 000000000..7be372949 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricAdapter.java @@ -0,0 +1,13 @@ +package org.dynmap.fabric_26_2; + +import net.minecraft.server.level.ServerLevel; +import org.dynmap.DynmapLocation; + +public final class FabricAdapter { + public static DynmapLocation toDynmapLocation(DynmapPlugin plugin, ServerLevel world, double x, double y, double z) { + return new DynmapLocation(plugin.getWorld(world).getName(), x, y, z); + } + + private FabricAdapter() { + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricCommandSender.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricCommandSender.java new file mode 100644 index 000000000..370e40cc0 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricCommandSender.java @@ -0,0 +1,45 @@ +package org.dynmap.fabric_26_2; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import org.dynmap.common.DynmapCommandSender; + +/* Handler for generic console command sender */ +public class FabricCommandSender implements DynmapCommandSender { + private CommandSourceStack sender; + + protected FabricCommandSender() { + sender = null; + } + + public FabricCommandSender(CommandSourceStack send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + sender.sendSuccess(() -> Component.literal(msg), false); + } + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public boolean hasPermissionNode(String node) { + return true; + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricLogger.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricLogger.java new file mode 100644 index 000000000..324d297ba --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricLogger.java @@ -0,0 +1,49 @@ +package org.dynmap.fabric_26_2; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dynmap.utils.DynmapLogger; + +public class FabricLogger implements DynmapLogger { + Logger log; + public static final String DM = "[Dynmap] "; + + FabricLogger() { + log = LogManager.getLogger("Dynmap"); + } + + @Override + public void info(String s) { + log.info(DM + s); + } + + @Override + public void severe(Throwable t) { + log.fatal(t); + } + + @Override + public void severe(String s) { + log.fatal(DM + s); + } + + @Override + public void severe(String s, Throwable t) { + log.fatal(DM + s, t); + } + + @Override + public void verboseinfo(String s) { + log.info(DM + s); + } + + @Override + public void warning(String s) { + log.warn(DM + s); + } + + @Override + public void warning(String s, Throwable t) { + log.warn(DM + s, t); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricMapChunkCache.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricMapChunkCache.java new file mode 100644 index 000000000..bb03efb9c --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricMapChunkCache.java @@ -0,0 +1,116 @@ +package org.dynmap.fabric_26_2; + +import net.minecraft.nbt.*; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCore; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkSection; +import org.dynmap.common.chunk.GenericMapChunkCache; +import org.dynmap.hdmap.HDBlockModels; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.renderer.RenderPatchFactory; +import org.dynmap.utils.*; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class FabricMapChunkCache extends GenericMapChunkCache { + private Level w; + private ServerChunkCache cps; + + /** + * Construct empty cache + */ + public FabricMapChunkCache(DynmapPlugin plugin) { + super(plugin.sscache); + } + + public void setChunks(FabricWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* World is always a ServerLevel for a server-side mod */ + cps = ((ServerLevel) this.w).getChunkSource(); + } + super.setChunks(dw, chunks); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + if (cps.hasChunk(chunk.x, chunk.z)) { + CompoundTag nbt = null; + try { + ChunkAccess loadedChunk = cps.getChunk(chunk.x, chunk.z, ChunkStatus.FULL, false); + SerializableChunkData sc = SerializableChunkData.copyOf((ServerLevel) w, loadedChunk); + nbt = sc.write(); + } catch (NullPointerException e) { + // TODO: find out why this is happening and why it only seems to happen since 1.16.2 + Log.severe("SerializableChunkData.write threw a NullPointerException", e); + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + + private CompoundTag readChunk(int x, int z) { + try { + // Async chunk reading is synchronized here. Perhaps we can do async and improve performance? + return cps.chunkMap.read(new net.minecraft.world.level.ChunkPos(x, z)).join().orElse(null); + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } + + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + CompoundTag nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject() + .map(Biome::getSpecialEffects) + .flatMap(BiomeSpecialEffects::foliageColorOverride) + .orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects effects = bm.getBiomeObject() + .map(Biome::getSpecialEffects) + .orElse(null); + + if (effects == null) return colormap[bm.biomeLookup()]; + + int baseColor = effects.grassColorOverride() + .orElse(colormap[bm.biomeLookup()]); + + BiomeSpecialEffects.GrassColorModifier modifier = effects.grassColorModifier(); + if (modifier != null) return modifier.modifyColor((double)x, (double)z, baseColor); + + return baseColor; + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricPlayer.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricPlayer.java new file mode 100644 index 000000000..bff2cff15 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricPlayer.java @@ -0,0 +1,257 @@ +package org.dynmap.fabric_26_2; + +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; + +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.Level; +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +/** + * Player access abstraction class + */ +public class FabricPlayer extends FabricCommandSender implements DynmapPlayer { + private static final Gson GSON = new GsonBuilder().create(); + private final DynmapPlugin plugin; + // FIXME: Proper setter + ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + public FabricPlayer(DynmapPlugin plugin, ServerPlayer player) { + this.plugin = plugin; + this.player = player; + String url = null; + if (this.player != null) { + uuid = this.player.getUUID(); + GameProfile prof = this.player.getGameProfile(); + if (prof != null) { + Property textureProperty = Iterables.getFirst(prof.properties().get("textures"), null); + + if (textureProperty != null) { + DynmapPlugin.TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(textureProperty.value()), StandardCharsets.UTF_8); + result = GSON.fromJson(json, DynmapPlugin.TexturesPayload.class); + } catch (JsonParseException e) { + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } else { + uuid = null; + } + skinurl = url; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public String getName() { + if (player != null) { + String n = player.getName().getString(); + ; + return n; + } else + return "[Server]"; + } + + @Override + public String getDisplayName() { + if (player != null) { + String n = player.getDisplayName().getString(); + return n; + } else + return "[Server]"; + } + + @Override + public boolean isOnline() { + return true; + } + + @Override + public DynmapLocation getLocation() { + if (player == null) { + return null; + } + + return FabricAdapter.toDynmapLocation(plugin, player.level(), player.getX(), player.getY(), player.getZ()); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + Level world = player.level(); + if (world != null) { + return plugin.getWorld(world).getName(); + } + + return null; + } + + @Override + public InetSocketAddress getAddress() { + if (player != null) { + ServerGamePacketListenerImpl networkHandler = player.connection; + if (networkHandler != null) { + SocketAddress sa = networkHandler.getRemoteAddress(); + if (sa instanceof InetSocketAddress) { + return (InetSocketAddress) sa; + } + } + } + return null; + } + + @Override + public boolean isSneaking() { + if (player != null) { + return player.isCrouching(); + } + + return false; + } + + @Override + public double getHealth() { + if (player != null) { + double h = player.getHealth(); + if (h > 20) h = 20; + return h; // Scale to 20 range + } else { + return 0; + } + } + + @Override + public int getArmorPoints() { + if (player != null) { + return player.getArmorValue(); + } else { + return 0; + } + } + + @Override + public DynmapLocation getBedSpawnLocation() { + return null; + } + + @Override + public long getLastLoginTime() { + return 0; + } + + @Override + public long getFirstLoginTime() { + return 0; + } + + @Override + public boolean hasPrivilege(String privid) { + if (player != null) + return plugin.hasPerm(player, privid); + return false; + } + + @Override + public boolean isOp() { + return plugin.isOp(player.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + @Override + public boolean isSpectator() { + if(player != null) { + return player.isSpectator(); + } + return false; + } + + @Override + public int getSortWeight() { + return plugin.getSortWeight(getName()); + } + + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + plugin.dropSortWeight(getName()); + } else { + plugin.setSortWeight(getName(), wt); + } + } + + @Override + public boolean hasPermissionNode(String node) { + return player != null && plugin.hasPermNode(player, node); + } + + @Override + public String getSkinURL() { + return skinurl; + } + + @Override + public UUID getUUID() { + return uuid; + } + + /** + * Send title and subtitle text (called from server thread) + */ + @Override + public void sendTitleText(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { + if (player != null) { + ServerPlayer player = this.player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks); + player.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket(Component.literal(title)); + player.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket(Component.literal(subtitle)); + player.connection.send(subtitlepkt); + } + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricServer.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricServer.java new file mode 100644 index 000000000..c037cd71a --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricServer.java @@ -0,0 +1,599 @@ +package org.dynmap.fabric_26_2; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.world.level.block.SignBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.players.IpBanList; +import net.minecraft.server.players.UserBanList; +import net.minecraft.server.players.NameAndId; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.fabric_26_2.event.BlockEvents; +import org.dynmap.fabric_26_2.event.ServerChatEvents; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +/** + * Server access abstraction class + */ +public class FabricServer extends DynmapServerInterface { + /* Server thread scheduler */ + private final Object schedlock = new Object(); + private final DynmapPlugin plugin; + private final MinecraftServer server; + private final Registry biomeRegistry; + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + private PriorityQueue runqueue = new PriorityQueue(); + + public FabricServer(DynmapPlugin plugin, MinecraftServer server) { + this.plugin = plugin; + this.server = server; + this.biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); + } + + private Optional getProfileByName(String playerName) { + return Optional.ofNullable(NameAndId.createOffline(playerName)); + } + + public final Registry getBiomeRegistry() { + return biomeRegistry; + } + + private Biome[] biomelist = null; + + public final Biome[] getBiomeList(Registry biomeRegistry) { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = biomeRegistry.iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = biomeRegistry.getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + @Override + public int getBlockIDAt(String wname, int x, int y, int z) { + return -1; + } + + @SuppressWarnings("deprecation") /* Not much I can do... fix this if it breaks. */ + @Override + public int isSignAt(String wname, int x, int y, int z) { + Level world = plugin.getWorldByName(wname).getWorld(); + + BlockPos pos = new BlockPos(x, y, z); + if (!world.hasChunkAt(pos)) + return -1; + + Block block = world.getBlockState(pos).getBlock(); + return (block instanceof SignBlock ? 1 : 0); + } + + @Override + public void scheduleServerTask(Runnable run, long delay) { + /* Add task record to queue */ + synchronized (schedlock) { + TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, new FutureTask(run, null)); + runqueue.add(tr); + } + } + + @Override + public DynmapPlayer[] getOnlinePlayers() { + if (server.getPlayerList() == null) return new DynmapPlayer[0]; + + List players = server.getPlayerList().getPlayers(); + int playerCount = players.size(); + DynmapPlayer[] dplay = new DynmapPlayer[players.size()]; + + for (int i = 0; i < playerCount; i++) { + ServerPlayer player = players.get(i); + dplay[i] = plugin.getOrAddPlayer(player); + } + + return dplay; + } + + @Override + public void reload() { + plugin.onDisable(); + plugin.onEnable(); + plugin.onStart(); + } + + @Override + public DynmapPlayer getPlayer(String name) { + List players = server.getPlayerList().getPlayers(); + + for (ServerPlayer player : players) { + + if (player.getName().getString().equalsIgnoreCase(name)) { + return plugin.getOrAddPlayer(player); + } + } + + return null; + } + + @Override + public Set getIPBans() { + IpBanList bl = server.getPlayerList().getIpBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + ips.add(s); + } + + return ips; + } + + @Override + public Future callSyncMethod(Callable task) { + return callSyncMethod(task, 0); + } + + public Future callSyncMethod(Callable task, long delay) { + FutureTask ft = new FutureTask(task); + + /* Add task record to queue */ + synchronized (schedlock) { + TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, ft); + runqueue.add(tr); + } + + return ft; + } + + void clearTaskQueue() { + this.runqueue.clear(); + } + + @Override + public String getServerName() { + String sn; + if (server.isSingleplayer()) + sn = "Integrated"; + else + sn = server.getLocalIp(); + if (sn == null) sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + PlayerList scm = server.getPlayerList(); + UserBanList bl = scm.getBans(); + + return getProfileByName(pid) + .map(profile -> bl.isBanned(profile)) + .orElse(true); + } + + @Override + public String stripChatColor(String s) { + return DynmapPlugin.patternControlCode.matcher(s).replaceAll(""); + } + + private Set registered = new HashSet(); + + @Override + public boolean requestEventNotification(DynmapListenerManager.EventType type) { + if (registered.contains(type)) { + return true; + } + + switch (type) { + case WORLD_LOAD: + case WORLD_UNLOAD: + /* Already called for normal world activation/deactivation */ + break; + + case WORLD_SPAWN_CHANGE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onSpawnChange(SpawnChangeEvent evt) { + DynmapWorld w = new BukkitWorld(evt.getWorld()); + core.listenerManager.processWorldEvent(EventType.WORLD_SPAWN_CHANGE, w); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_JOIN: + case PLAYER_QUIT: + /* Already handled */ + break; + + case PLAYER_BED_LEAVE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onPlayerBedLeave(PlayerBedLeaveEvent evt) { + DynmapPlayer p = new BukkitPlayer(evt.getPlayer()); + core.listenerManager.processPlayerEvent(EventType.PLAYER_BED_LEAVE, p); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_CHAT: + if (plugin.chathandler == null) { + plugin.setChatHandler(new DynmapPlugin.ChatHandler(plugin)); + ServerChatEvents.EVENT.register((player, message) -> plugin.chathandler.handleChat(player, message)); + } + break; + + case BLOCK_BREAK: + /* Already handled by BlockEvents logic */ + break; + + case SIGN_CHANGE: + BlockEvents.SIGN_CHANGE_EVENT.register((world, pos, lines, player, front) -> { + plugin.core.processSignChange("fabric", FabricWorld.getWorldName(plugin, world), + pos.getX(), pos.getY(), pos.getZ(), lines, player.getName().getString()); + }); + break; + + default: + Log.severe("Unhandled event type: " + type); + return false; + } + + registered.add(type); + return true; + } + + @Override + public boolean sendWebChatEvent(String source, String name, String msg) { + return DynmapCommonAPIListener.fireWebChatEvent(source, name, msg); + } + + @Override + public void broadcastMessage(String msg) { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, false); + Log.info(stripChatColor(msg)); + } + + @Override + public String[] getBiomeIDs() { + BiomeMap[] b = BiomeMap.values(); + String[] bname = new String[b.length]; + + for (int i = 0; i < bname.length; i++) { + bname[i] = b[i].toString(); + } + + return bname; + } + + @Override + public double getCacheHitRate() { + if (plugin.sscache != null) + return plugin.sscache.getHitRate(); + return 0.0; + } + + @Override + public void resetCacheStats() { + if (plugin.sscache != null) + plugin.sscache.resetStats(); + } + + @Override + public DynmapWorld getWorldByName(String wname) { + return plugin.getWorldByName(wname); + } + + @Override + public DynmapPlayer getOfflinePlayer(String name) { + /* + OfflinePlayer op = getServer().getOfflinePlayer(name); + if(op != null) { + return new BukkitPlayer(op); + } + */ + return null; + } + + @Override + public Set checkPlayerPermissions(String player, Set perms) { + if (isPlayerBanned(player)) { + return Collections.emptySet(); + } + Set rslt = plugin.hasOfflinePermissions(player, perms); + if (rslt == null) { + rslt = new HashSet(); + if (plugin.isOp(player)) { + rslt.addAll(perms); + } + } + return rslt; + } + + @Override + public boolean checkPlayerPermission(String player, String perm) { + if (isPlayerBanned(player)) { + return false; + } + return plugin.hasOfflinePermission(player, perm); + } + + /** + * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread + */ + @Override + public MapChunkCache createMapChunkCache(DynmapWorld w, List chunks, + boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) { + FabricMapChunkCache c = (FabricMapChunkCache) w.getChunkCache(chunks); + if (c == null) { + return null; + } + if (w.visibility_limits != null) { + for (VisibilityLimit limit : w.visibility_limits) { + c.setVisibleRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (w.hidden_limits != null) { + for (VisibilityLimit limit : w.hidden_limits) { + c.setHiddenRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (!c.setChunkDataTypes(blockdata, biome, highesty, rawbiome)) { + Log.severe("CraftBukkit build does not support biome APIs"); + } + + if (chunks.size() == 0) /* No chunks to get? */ { + c.loadChunks(0); + return c; + } + + //Now handle any chunks in server thread that are already loaded (on server thread) + final FabricMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException cx) { + return null; + } catch (ExecutionException xx) { + Log.severe("Exception while loading chunks", xx.getCause()); + return null; + } catch (Exception ix) { + Log.severe(ix); + return null; + } + if (!w.isLoaded()) { + return null; + } + // Now, do rest of chunk reading from calling thread + c.readChunks(chunks.size()); + + return c; + } + + @Override + public int getMaxPlayers() { + return server.getPlayerList().getMaxPlayers(); + } + + @Override + public int getCurrentPlayers() { + return server.getPlayerList().getPlayerCount(); + } + + public void tickEvent(MinecraftServer server) { + cur_tick_starttime = System.nanoTime(); + long elapsed = cur_tick_starttime - plugin.lasttick; + plugin.lasttick = cur_tick_starttime; + plugin.avgticklen = ((plugin.avgticklen * 99) / 100) + (elapsed / 100); + plugin.tps = (double) 1E9 / (double) plugin.avgticklen; + // Tick core + if (plugin.core != null) { + plugin.core.serverTick(plugin.tps); + } + + boolean done = false; + TaskRecord tr = null; + + while (!plugin.blockupdatequeue.isEmpty()) { + DynmapPlugin.BlockUpdateRec r = plugin.blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(DynmapPlugin.stateByID[idx])) { + if (plugin.onblockchange_with_id) + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange"); + } + } + + long now; + + synchronized (schedlock) { + cur_tick++; + now = System.nanoTime(); + tr = runqueue.peek(); + /* Nothing due to run */ + if ((tr == null) || (tr.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) { + done = true; + } else { + tr = runqueue.poll(); + } + } + while (!done) { + tr.run(); + + synchronized (schedlock) { + tr = runqueue.peek(); + now = System.nanoTime(); + /* Nothing due to run */ + if ((tr == null) || (tr.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) { + done = true; + } else { + tr = runqueue.poll(); + } + } + } + while (!plugin.msgqueue.isEmpty()) { + DynmapPlugin.ChatMessage cm = plugin.msgqueue.poll(); + DynmapPlayer dp = null; + if (cm.sender != null) + dp = plugin.getOrAddPlayer(cm.sender); + else + dp = new FabricPlayer(plugin, null); + + plugin.core.listenerManager.processChatEvent(DynmapListenerManager.EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + private Optional getModContainerById(String id) { + return FabricLoader.getInstance().getModContainer(id); + } + + @Override + public boolean isModLoaded(String name) { + return FabricLoader.getInstance().getModContainer(name).isPresent(); + } + + @Override + public String getModVersion(String name) { + Optional mod = getModContainerById(name); // Try case sensitive lookup + return mod.map(modContainer -> modContainer.getMetadata().getVersion().getFriendlyString()).orElse(null); + } + + @Override + public double getServerTPS() { + return plugin.tps; + } + + @Override + public String getServerIP() { + if (server.isSingleplayer()) + return "0.0.0.0"; + else + return server.getLocalIp(); + } + + @Override + public File getModContainerFile(String name) { + Optional container = getModContainerById(name); // Try case sensitive lookup + if (container.isPresent()) { + Path path = container.get().getRootPath(); + if (path.getFileSystem().provider().getScheme().equals("jar")) { + path = Paths.get(path.getFileSystem().toString()); + } + return path.toFile(); + } + return null; + } + + @Override + public List getModList() { + return FabricLoader.getInstance() + .getAllMods() + .stream() + .map(container -> container.getMetadata().getId()) + .collect(Collectors.toList()); + } + + @Override + public Map getBlockIDMap() { + Map map = new HashMap(); + return map; + } + + @Override + public InputStream openResource(String modid, String rname) { + if (modid == null) modid = "minecraft"; + + if ("minecraft".equals(modid)) { + return MinecraftServer.class.getClassLoader().getResourceAsStream(rname); + } else { + if (rname.startsWith("/") || rname.startsWith("\\")) { + rname = rname.substring(1); + } + + final String finalModid = modid; + final String finalRname = rname; + return getModContainerById(modid).map(container -> { + try { + return Files.newInputStream(container.getPath(finalRname)); + } catch (IOException e) { + Log.severe("Failed to load resource of mod :" + finalModid, e); + return null; + } + }).orElse(null); + } + } + + /** + * Get block unique ID map (module:blockid) + */ + @Override + public Map getBlockUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + + /** + * Get item unique ID map (module:itemid) + */ + @Override + public Map getItemUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricWorld.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricWorld.java new file mode 100644 index 000000000..485c0aa35 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/FabricWorld.java @@ -0,0 +1,238 @@ +package org.dynmap.fabric_26_2; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.Level; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.border.WorldBorder; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +import java.util.List; + +public class FabricWorld extends DynmapWorld { + // TODO: Store this relative to World saves for integrated server + public static final String SAVED_WORLDS_FILE = "fabricworlds.yml"; + + private final DynmapPlugin plugin; + private Level world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(DynmapPlugin plugin, Level w) { + ResourceKey rk = w.dimension(); + if (rk == Level.OVERWORLD) { // Overworld? + return w.getServer().getWorldData().getLevelName(); + } else if (rk == Level.END) { + return "DIM1"; + } else if (rk == Level.NETHER) { + return "DIM-1"; + } else { + return rk.identifier().getNamespace() + "_" + rk.identifier().getPath(); + } + } + + public void updateWorld(Level w) { + this.updateWorldHeights(w.getHeight(), w.getMinY(), ((ServerLevel) w).getSeaLevel()); + } + + public FabricWorld(DynmapPlugin plugin, Level w) { + this(plugin, getWorldName(plugin, w), w.getHeight(), + ((ServerLevel) w).getSeaLevel(), + w.dimension() == Level.NETHER, + w.dimension() == Level.END, + w.dimension().identifier().getPath(), + w.getMinY()); + setWorldLoaded(w); + } + + public FabricWorld(DynmapPlugin plugin, String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) { + super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny); + this.plugin = plugin; + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) { + env = "nether"; + } else if (istheend) { + env = "the_end"; + } else { + env = "normal"; + } + + } + + /* Test if world is nether */ + @Override + public boolean isNether() { + return isnether; + } + + public boolean isTheEnd() { + return istheend; + } + + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() { + if (world != null) { + BlockPos spawnPos = world.getLevelData().getRespawnData().pos().immutable(); + spawnloc.x = spawnPos.getX(); + spawnloc.y = spawnPos.getY(); + spawnloc.z = spawnPos.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + + /* Get world time */ + @Override + public long getTime() { + if (world != null) + return world.getOverworldClockTime(); + else + return -1; + } + + /* World is storming */ + @Override + public boolean hasStorm() { + if (world != null) + return world.isRaining(); + else + return false; + } + + /* World is thundering */ + @Override + public boolean isThundering() { + if (world != null) + return world.isThundering(); + else + return false; + } + + /* World is loaded */ + @Override + public boolean isLoaded() { + return (world != null); + } + + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() { + getSpawnLocation(); + world = null; + } + + /* Set world to loaded */ + public void setWorldLoaded(Level w) { + world = w; + this.sealevel = ((ServerLevel) w).getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int lightLevel = 0; lightLevel < 16; lightLevel++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) lightLevel / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(lightLevel, Mth.lerp(w.dimensionType().ambientLight(), brightness, 1.0F)); + } + } + + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) { + if (world != null) + return world.getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) { + if (world != null) { + return world.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); + } else + return -1; + } + + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() { + return skylight; + } + + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) { + if (world != null) { + return world.getLightEngine().getLayerListener(LightLayer.SKY).getLightValue(new BlockPos(x, y, z)); + } else + return -1; + } + + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() { + return env; + } + + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) { + if (world != null) { + FabricMapChunkCache c = new FabricMapChunkCache(plugin); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public Level getWorld() { + return world; + } + + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/NBT.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/NBT.java new file mode 100644 index 000000000..644d72b61 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/NBT.java @@ -0,0 +1,139 @@ +package org.dynmap.fabric_26_2; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Set; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.keySet(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + // Like contains, but with an extra constraint on type + Tag base = obj.get(s); + if (base == null) + return false; + int type = base.getId(); + if (type == i) + return true; + else if (i != TAG_ANY_NUMERIC) + return false; + return type == TAG_BYTE || type == TAG_SHORT || type == TAG_INT || type == TAG_LONG || type == TAG_FLOAT + || type == TAG_DOUBLE; + } + @Override + public byte getByte(String s) { + return obj.getByteOr(s, (byte)0); + } + @Override + public short getShort(String s) { + return obj.getShortOr(s, (short)0); + } + @Override + public int getInt(String s) { + return obj.getIntOr(s, (int)0); + } + @Override + public long getLong(String s) { + return obj.getLongOr(s, (long)0); + } + @Override + public float getFloat(String s) { + return obj.getFloatOr(s, (float)0); + } + @Override + public double getDouble(String s) { + return obj.getDoubleOr(s, (double)0); + } + @Override + public String getString(String s) { + return obj.getStringOr(s, ""); + } + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s).orElseGet(() -> new byte[0]); + } + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s).orElseGet(() -> new int[0]); + } + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s).orElseGet(() -> new long[0]); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompoundOrEmpty(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + // i argument used to be used to constrain list type, but nbt lists no longer have types as of 1.21.5 + return new NBTList(obj.getListOrEmpty(s)); + } + @Override + public boolean getBoolean(String s) { + return obj.getBooleanOr(s, false); + } + @Override + public String getAsString(String s) { + Tag t = obj.get(s); + return (t != null) ? t.asString().orElseGet(() -> "") : ""; + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + public static class NBTList implements GenericNBTList { + private final ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getStringOr(idx, ""); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompoundOrEmpty(idx)); + } + public String toString() { + return obj.toString(); + } + } + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/TaskRecord.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/TaskRecord.java new file mode 100644 index 000000000..314e59b80 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/TaskRecord.java @@ -0,0 +1,38 @@ +package org.dynmap.fabric_26_2; + +import java.util.concurrent.FutureTask; + +class TaskRecord implements Comparable { + TaskRecord(long ticktorun, long id, FutureTask future) { + this.ticktorun = ticktorun; + this.id = id; + this.future = future; + } + + private final long ticktorun; + private final long id; + private final FutureTask future; + + void run() { + this.future.run(); + } + + long getTickToRun() { + return this.ticktorun; + } + + @Override + public int compareTo(TaskRecord o) { + if (this.ticktorun < o.ticktorun) { + return -1; + } else if (this.ticktorun > o.ticktorun) { + return 1; + } else if (this.id < o.id) { + return -1; + } else if (this.id > o.id) { + return 1; + } else { + return 0; + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/VersionCheck.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/VersionCheck.java new file mode 100644 index 000000000..71189020e --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/VersionCheck.java @@ -0,0 +1,98 @@ +package org.dynmap.fabric_26_2; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for (int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) { + } + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(index + 1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if ((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while ((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while ((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if (split.length < 4) continue; + /* If our platform and version, or wildcard platform version */ + if (split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if ((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } else if (cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if ((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmapCommand.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmapCommand.java new file mode 100644 index 000000000..05028dfd1 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmapCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_26_2.command; + +import org.dynmap.fabric_26_2.DynmapPlugin; + +public class DmapCommand extends DynmapCommandExecutor { + public DmapCommand(DynmapPlugin p) { + super("dmap", p); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmarkerCommand.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmarkerCommand.java new file mode 100644 index 000000000..5429e3025 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DmarkerCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_26_2.command; + +import org.dynmap.fabric_26_2.DynmapPlugin; + +public class DmarkerCommand extends DynmapCommandExecutor { + public DmarkerCommand(DynmapPlugin p) { + super("dmarker", p); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommand.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommand.java new file mode 100644 index 000000000..a3db9928f --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_26_2.command; + +import org.dynmap.fabric_26_2.DynmapPlugin; + +public class DynmapCommand extends DynmapCommandExecutor { + public DynmapCommand(DynmapPlugin p) { + super("dynmap", p); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommandExecutor.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommandExecutor.java new file mode 100644 index 000000000..b46db2215 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapCommandExecutor.java @@ -0,0 +1,64 @@ +package org.dynmap.fabric_26_2.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import net.minecraft.commands.CommandSourceStack; + +import java.util.Arrays; + +import org.dynmap.fabric_26_2.DynmapPlugin; + +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class DynmapCommandExecutor implements Command { + private final String cmd; + private final DynmapPlugin plugin; + + DynmapCommandExecutor(String cmd, DynmapPlugin plugin) { + this.cmd = cmd; + this.plugin = plugin; + } + + public void register(CommandDispatcher dispatcher) { + final RootCommandNode root = dispatcher.getRoot(); + + final LiteralCommandNode command = literal(this.cmd) + .executes(this) + .build(); + + final ArgumentCommandNode args = argument("args", greedyString()) + .executes(this) + .build(); + + // So this becomes "cmd" [args] + command.addChild(args); + + // Add command to the command dispatcher via root node. + root.addChild(command); + } + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + // Commands in brigadier may be proxied in Minecraft via a syntax like `/execute ... ... run dmap [args]` + // Dynmap will fail to parse this properly, so we find the starting position of the actual command being parsed after any forks or redirects. + // The start position of the range specifies where the actual command dynmap has registered starts + int start = context.getRange().getStart(); + String dynmapInput = context.getInput().substring(start); + + String[] args = dynmapInput.split("\\s+"); + plugin.handleCommand(context.getSource(), cmd, Arrays.copyOfRange(args, 1, args.length)); + return 1; + } + + // @Override // TODO: Usage? + public String getUsage(CommandSourceStack commandSource) { + return "Run /" + cmd + " help for details on using command"; + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapExpCommand.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapExpCommand.java new file mode 100644 index 000000000..f4c5cb719 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/command/DynmapExpCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_26_2.command; + +import org.dynmap.fabric_26_2.DynmapPlugin; + +public class DynmapExpCommand extends DynmapCommandExecutor { + public DynmapExpCommand(DynmapPlugin p) { + super("dynmapexp", p); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/BlockEvents.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/BlockEvents.java new file mode 100644 index 000000000..1fb9b4b18 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/BlockEvents.java @@ -0,0 +1,39 @@ +package org.dynmap.fabric_26_2.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; + +public class BlockEvents { + private BlockEvents() { + } + + public static Event BLOCK_EVENT = EventFactory.createArrayBacked(BlockCallback.class, + (listeners) -> (world, pos) -> { + for (BlockCallback callback : listeners) { + callback.onBlockEvent(world, pos); + } + } + ); + + public static Event SIGN_CHANGE_EVENT = EventFactory.createArrayBacked(SignChangeCallback.class, + (listeners) -> (world, pos, lines, player, front) -> { + for (SignChangeCallback callback : listeners) { + callback.onSignChange(world, pos, lines, player, front); + } + } + ); + + @FunctionalInterface + public interface BlockCallback { + void onBlockEvent(Level world, BlockPos pos); + } + + @FunctionalInterface + public interface SignChangeCallback { + void onSignChange(ServerLevel world, BlockPos pos, String[] lines, ServerPlayer player, boolean front); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/CustomServerLifecycleEvents.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/CustomServerLifecycleEvents.java new file mode 100644 index 000000000..993187636 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/CustomServerLifecycleEvents.java @@ -0,0 +1,14 @@ +package org.dynmap.fabric_26_2.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CustomServerLifecycleEvents { + public static final Event SERVER_STARTED_PRE_WORLD_LOAD = + EventFactory.createArrayBacked(ServerLifecycleEvents.ServerStarted.class, (callbacks) -> (server) -> { + for (ServerLifecycleEvents.ServerStarted callback : callbacks) { + callback.onServerStarted(server); + } + }); +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/PlayerEvents.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/PlayerEvents.java new file mode 100644 index 000000000..1d5faf228 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/PlayerEvents.java @@ -0,0 +1,62 @@ +package org.dynmap.fabric_26_2.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.level.ServerPlayer; + +public class PlayerEvents { + private PlayerEvents() { + } + + public static Event PLAYER_LOGGED_IN = EventFactory.createArrayBacked(PlayerLoggedIn.class, + (listeners) -> (player) -> { + for (PlayerLoggedIn callback : listeners) { + callback.onPlayerLoggedIn(player); + } + } + ); + + public static Event PLAYER_LOGGED_OUT = EventFactory.createArrayBacked(PlayerLoggedOut.class, + (listeners) -> (player) -> { + for (PlayerLoggedOut callback : listeners) { + callback.onPlayerLoggedOut(player); + } + } + ); + + public static Event PLAYER_CHANGED_DIMENSION = EventFactory.createArrayBacked(PlayerChangedDimension.class, + (listeners) -> (player) -> { + for (PlayerChangedDimension callback : listeners) { + callback.onPlayerChangedDimension(player); + } + } + ); + + public static Event PLAYER_RESPAWN = EventFactory.createArrayBacked(PlayerRespawn.class, + (listeners) -> (player) -> { + for (PlayerRespawn callback : listeners) { + callback.onPlayerRespawn(player); + } + } + ); + + @FunctionalInterface + public interface PlayerLoggedIn { + void onPlayerLoggedIn(ServerPlayer player); + } + + @FunctionalInterface + public interface PlayerLoggedOut { + void onPlayerLoggedOut(ServerPlayer player); + } + + @FunctionalInterface + public interface PlayerChangedDimension { + void onPlayerChangedDimension(ServerPlayer player); + } + + @FunctionalInterface + public interface PlayerRespawn { + void onPlayerRespawn(ServerPlayer player); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/ServerChatEvents.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/ServerChatEvents.java new file mode 100644 index 000000000..6cb4b4b48 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/event/ServerChatEvents.java @@ -0,0 +1,23 @@ +package org.dynmap.fabric_26_2.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.level.ServerPlayer; + +public class ServerChatEvents { + private ServerChatEvents() { + } + + public static Event EVENT = EventFactory.createArrayBacked(ServerChatCallback.class, + (listeners) -> (player, message) -> { + for (ServerChatCallback callback : listeners) { + callback.onChatMessage(player, message); + } + } + ); + + @FunctionalInterface + public interface ServerChatCallback { + void onChatMessage(ServerPlayer player, String message); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/MinecraftServerMixin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/MinecraftServerMixin.java new file mode 100644 index 000000000..9cf1102f0 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/MinecraftServerMixin.java @@ -0,0 +1,17 @@ +package org.dynmap.fabric_26_2.mixin; + +import net.minecraft.server.MinecraftServer; + +import org.dynmap.fabric_26_2.event.CustomServerLifecycleEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftServer.class) +public class MinecraftServerMixin { + @Inject(method = "loadLevel", at = @At("HEAD")) + protected void loadLevel(String levelName, CallbackInfo info) { + CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.invoker().onServerStarted((MinecraftServer) (Object) this); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/PlayerManagerMixin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/PlayerManagerMixin.java new file mode 100644 index 000000000..e5a67e09c --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/PlayerManagerMixin.java @@ -0,0 +1,32 @@ +package org.dynmap.fabric_26_2.mixin; + +import net.minecraft.world.entity.Entity; +import net.minecraft.network.Connection; +import net.minecraft.server.players.PlayerList; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.level.ServerPlayer; + +import org.dynmap.fabric_26_2.event.PlayerEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerList.class) +public class PlayerManagerMixin { + @Inject(method = "placeNewPlayer", at = @At("TAIL")) + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie ccd, CallbackInfo info) { + PlayerEvents.PLAYER_LOGGED_IN.invoker().onPlayerLoggedIn(player); + } + + @Inject(method = "remove", at = @At("HEAD")) + public void remove(ServerPlayer player, CallbackInfo info) { + PlayerEvents.PLAYER_LOGGED_OUT.invoker().onPlayerLoggedOut(player); + } + + @Inject(method = "respawn", at = @At("RETURN")) + public void respawn(ServerPlayer player, boolean alive, Entity.RemovalReason removalReason, CallbackInfoReturnable info) { + PlayerEvents.PLAYER_RESPAWN.invoker().onPlayerRespawn(info.getReturnValue()); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayNetworkHandlerMixin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayNetworkHandlerMixin.java new file mode 100644 index 000000000..6fe860c1e --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayNetworkHandlerMixin.java @@ -0,0 +1,35 @@ +package org.dynmap.fabric_26_2.mixin; + +import net.minecraft.network.protocol.game.ServerboundChatPacket; +import net.minecraft.network.protocol.game.ServerboundSignUpdatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; + +import org.dynmap.fabric_26_2.event.BlockEvents; +import org.dynmap.fabric_26_2.event.ServerChatEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerGamePacketListenerImpl.class) +public abstract class ServerPlayNetworkHandlerMixin { + @Shadow + public ServerPlayer player; + + // Injected at HEAD (before Mojang's chat filtering runs) so Dynmap sees the raw, unfiltered message. + @Inject(method = "handleChat", at = @At("HEAD")) + public void onGameMessage(ServerboundChatPacket packet, CallbackInfo ci) { + ServerChatEvents.EVENT.invoker().onChatMessage(player, packet.message()); + } + + // Injected at HEAD (before Mojang's profanity filtering runs) so Dynmap sees the raw sign text + // the player actually typed, same intent as the old local-capture/cancel-and-replay approach, + // but reading straight from the packet instead (which now exposes getLines()/getPos() directly). + @Inject(method = "handleSignUpdate", at = @At("HEAD")) + public void onSignUpdate(ServerboundSignUpdatePacket packet, CallbackInfo ci) { + BlockEvents.SIGN_CHANGE_EVENT.invoker().onSignChange((ServerLevel) player.level(), packet.getPos(), packet.getLines(), player, packet.isFrontText()); + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayerEntityMixin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayerEntityMixin.java new file mode 100644 index 000000000..a6b945c84 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/ServerPlayerEntityMixin.java @@ -0,0 +1,22 @@ +package org.dynmap.fabric_26_2.mixin; + +import net.minecraft.world.entity.Entity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.portal.TeleportTransition; + +import org.dynmap.fabric_26_2.event.PlayerEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ServerPlayer.class) +public class ServerPlayerEntityMixin { + @Inject(method = "teleport(Lnet/minecraft/world/level/portal/TeleportTransition;)Lnet/minecraft/world/entity/Entity;", at = @At("TAIL")) + public void teleport(TeleportTransition teleportTransition, CallbackInfoReturnable info) { + ServerPlayer player = (ServerPlayer) (Object) this; + if (player.getRemovalReason() == null) { + PlayerEvents.PLAYER_CHANGED_DIMENSION.invoker().onPlayerChangedDimension(player); + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/WorldChunkMixin.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/WorldChunkMixin.java new file mode 100644 index 000000000..d902b4d0d --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/mixin/WorldChunkMixin.java @@ -0,0 +1,26 @@ +package org.dynmap.fabric_26_2.mixin; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunk; + +import org.dynmap.fabric_26_2.event.BlockEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(LevelChunk.class) +public abstract class WorldChunkMixin { + @Shadow + public abstract Level getLevel(); + + @Inject(method = "setBlockState", at = @At("RETURN")) + public void setBlockState(BlockPos pos, BlockState state, int flags, CallbackInfoReturnable info) { + if (info.getReturnValue() != null) { + BlockEvents.BLOCK_EVENT.invoker().onBlockEvent(this.getLevel(), pos); + } + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FabricPermissions.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FabricPermissions.java new file mode 100644 index 000000000..70e1b63ab --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FabricPermissions.java @@ -0,0 +1,47 @@ +package org.dynmap.fabric_26_2.permissions; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.world.entity.player.Player; +import org.dynmap.Log; +import org.dynmap.fabric_26_2.DynmapPlugin; +import org.dynmap.json.simple.parser.JSONParser; + +import java.util.Set; +import java.util.stream.Collectors; + +public class FabricPermissions implements PermissionProvider { + + private String permissionKey(String perm) { + return "dynmap." + perm; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + return perms.stream() + .filter(perm -> hasOfflinePermission(player, perm)) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player.toLowerCase()); + } + + @Override + public boolean has(Player player, String permission) { + if (player == null) return false; + String name = player.getName().getString().toLowerCase(); + if (DynmapPlugin.plugin.isOp(name)) return true; + return Permissions.check(player, permissionKey(permission)); + } + + @Override + public boolean hasPermissionNode(Player player, String permission) { + if (player != null) { + String name = player.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(name); + } + return false; + } + +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FilePermissions.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FilePermissions.java new file mode 100644 index 000000000..acb119a35 --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/FilePermissions.java @@ -0,0 +1,103 @@ +package org.dynmap.fabric_26_2.permissions; + +import net.minecraft.world.entity.player.Player; +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.fabric_26_2.DynmapPlugin; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if (!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for (String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if (p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for (String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if (k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if ((ps != null) && (ps.contains(perm))) { + return true; + } + if (defperms.contains(perm)) { + return true; + } + return false; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } else { + for (String p : perms) { + if (hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if (DynmapPlugin.plugin.isOp(player)) { + return true; + } else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(Player psender, String permission) { + if (psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + + @Override + public boolean hasPermissionNode(Player psender, String permission) { + if (psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/LuckPermissions.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/LuckPermissions.java new file mode 100644 index 000000000..ea3337d3e --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/LuckPermissions.java @@ -0,0 +1,102 @@ +package org.dynmap.fabric_26_2.permissions; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.cacheddata.CachedPermissionData; +import net.luckperms.api.model.user.User; +import net.luckperms.api.util.Tristate; +import net.minecraft.world.entity.player.Player; +import net.minecraft.server.MinecraftServer; +import org.dynmap.Log; +import org.dynmap.fabric_26_2.DynmapPlugin; +import org.dynmap.json.simple.JSONArray; +import org.dynmap.json.simple.JSONObject; +import org.dynmap.json.simple.parser.JSONParser; +import org.dynmap.json.simple.parser.ParseException; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class LuckPermissions implements PermissionProvider { + + private final JSONParser parser = new JSONParser(); + private LuckPerms api = null; + + private Optional getApi() { + if (api != null) return Optional.of(api); + try { + api = LuckPermsProvider.get(); + return Optional.of(api); + } catch (Exception ex) { + Log.warning("Trying to access LuckPerms before it has loaded"); + return Optional.empty(); + } + } + + private Optional cachedUUID(String username) { + try { + BufferedReader reader = new BufferedReader(new FileReader("usercache.json")); + JSONArray cache = (JSONArray) parser.parse(reader); + for (Object it : cache) { + JSONObject user = (JSONObject) it; + if (user.get("name").toString().equalsIgnoreCase(username)) { + String uuid = user.get("uuid").toString(); + return Optional.of(UUID.fromString(uuid)); + } + } + + reader.close(); + } catch (IOException | ParseException ex) { + Log.warning("Unable to read usercache.json"); + } + + return Optional.empty(); + } + + private String permissionKey(String perm) { + return "dynmap." + perm; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + return perms.stream() + .filter(perm -> hasOfflinePermission(player, perm)) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + if (DynmapPlugin.plugin.isOp(player.toLowerCase())) return true; + Optional api = getApi(); + Optional uuid = cachedUUID(player); + if (!uuid.isPresent() || !api.isPresent()) return false; + User user = api.get().getUserManager().loadUser(uuid.get()).join(); + CachedPermissionData permissions = user.getCachedData().getPermissionData(); + Tristate state = permissions.checkPermission(permissionKey(perm)); + return state.asBoolean(); + } + + @Override + public boolean has(Player player, String permission) { + if (player == null) return false; + String name = player.getName().getString().toLowerCase(); + if (DynmapPlugin.plugin.isOp(name)) return true; + return Permissions.check(player, permissionKey(permission)); + } + + @Override + public boolean hasPermissionNode(Player player, String permission) { + if (player != null) { + String name = player.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(name); + } + return false; + } + +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/OpPermissions.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/OpPermissions.java new file mode 100644 index 000000000..873d801ba --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/OpPermissions.java @@ -0,0 +1,52 @@ +package org.dynmap.fabric_26_2.permissions; + +import net.minecraft.world.entity.player.Player; +import org.dynmap.Log; +import org.dynmap.fabric_26_2.DynmapPlugin; + +import java.util.HashSet; +import java.util.Set; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(Player psender, String permission) { + if (psender != null) { + if (usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + + @Override + public boolean hasPermissionNode(Player psender, String permission) { + if (psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/PermissionProvider.java b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/PermissionProvider.java new file mode 100644 index 000000000..b414d68fa --- /dev/null +++ b/fabric-26.2/src/main/java/org/dynmap/fabric_26_2/permissions/PermissionProvider.java @@ -0,0 +1,16 @@ +package org.dynmap.fabric_26_2.permissions; + +import net.minecraft.world.entity.player.Player; + +import java.util.Set; + +public interface PermissionProvider { + boolean has(Player sender, String permission); + + boolean hasPermissionNode(Player sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/fabric-26.2/src/main/resources/assets/dynmap/icon.png b/fabric-26.2/src/main/resources/assets/dynmap/icon.png new file mode 100644 index 000000000..d18f3e145 Binary files /dev/null and b/fabric-26.2/src/main/resources/assets/dynmap/icon.png differ diff --git a/fabric-26.2/src/main/resources/configuration.txt b/fabric-26.2/src/main/resources/configuration.txt new file mode 100644 index 000000000..bb8b456ca --- /dev/null +++ b/fabric-26.2/src/main/resources/configuration.txt @@ -0,0 +1,498 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + #override_endpoint: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + # Remember to change the following class to org.dynmap.JsonFileClientUpdateComponent when using an external web server. + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # optional, if true, players that are in spectator mode will be hidden + hideifspectator: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100, webp-l), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The path where files can be imported for /dmarker commands +importpath: import + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/fabric-26.2/src/main/resources/dynmap.mixins.json b/fabric-26.2/src/main/resources/dynmap.mixins.json new file mode 100644 index 000000000..d2c99aadc --- /dev/null +++ b/fabric-26.2/src/main/resources/dynmap.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.dynmap.fabric_26_2.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "MinecraftServerMixin", + "PlayerManagerMixin", + "ServerPlayerEntityMixin", + "ServerPlayNetworkHandlerMixin", + "WorldChunkMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-26.2/src/main/resources/fabric.mod.json b/fabric-26.2/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..898dd7a68 --- /dev/null +++ b/fabric-26.2/src/main/resources/fabric.mod.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "id": "dynmap", + "version": "3.4.0-beta-1", + "name": "Dynmap", + "description": "Dynamic, Google-maps style rendered maps for your Minecraft server", + "authors": [ + "mikeprimm", + "LolHens", + "i509VCB" + ], + "contact": { + "homepage": "https://github.com/webbukkit/dynmap", + "sources": "https://github.com/webbukkit/dynmap" + }, + "license": "Apache-2.0", + "icon": "assets/dynmap/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "org.dynmap.fabric_26_2.DynmapMod" + ] + }, + "mixins": [ + "dynmap.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.19.3", + "fabric": ">=0.155.2", + "minecraft": ["26.2"] + } +} diff --git a/fabric-26.2/src/main/resources/permissions.yml.example b/fabric-26.2/src/main/resources/permissions.yml.example new file mode 100644 index 000000000..a25f9adca --- /dev/null +++ b/fabric-26.2/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/forge-1.14.4/build.gradle b/forge-1.14.4/build.gradle index 628cd762c..f5a2593f2 100644 --- a/forge-1.14.4/build.gradle +++ b/forge-1.14.4/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" @@ -32,7 +36,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.14.4" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.14.4" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.15.2/build.gradle b/forge-1.15.2/build.gradle index a5ec88f19..2aa4f65a6 100644 --- a/forge-1.15.2/build.gradle +++ b/forge-1.15.2/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" @@ -32,7 +36,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.15.2" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.15.2" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.16.5/build.gradle b/forge-1.16.5/build.gradle index 866951553..3e33c2947 100644 --- a/forge-1.16.5/build.gradle +++ b/forge-1.16.5/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" @@ -32,7 +36,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.16.5" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.16.5" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.17.1/build.gradle b/forge-1.17.1/build.gradle index c17fbc592..4a2d703c0 100644 --- a/forge-1.17.1/build.gradle +++ b/forge-1.17.1/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(16) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('16') + targetCompatibility = JavaVersion.toVersion('16') +} +compileJava.sourceCompatibility = '16' +compileJava.targetCompatibility = '16' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -34,7 +38,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.17.1" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.17.1" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.18.2/build.gradle b/forge-1.18.2/build.gradle index 6ef40fd9c..1d9cd7a98 100644 --- a/forge-1.18.2/build.gradle +++ b/forge-1.18.2/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -34,7 +38,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.18.2" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.18.2" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.19.3/build.gradle b/forge-1.19.3/build.gradle index fd1a89f10..cd8aed1f8 100644 --- a/forge-1.19.3/build.gradle +++ b/forge-1.19.3/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('17') + targetCompatibility = JavaVersion.toVersion('17') +} +compileJava.sourceCompatibility = '17' +compileJava.targetCompatibility = '17' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -34,7 +38,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.19.3" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.19.3" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.20.6/build.gradle b/forge-1.20.6/build.gradle index 58413c933..80b6efaa7 100644 --- a/forge-1.20.6/build.gradle +++ b/forge-1.20.6/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -36,7 +40,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.20.6" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.20.6" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.21.10/build.gradle b/forge-1.21.10/build.gradle index 262083a7e..c3f0abd2b 100644 --- a/forge-1.21.10/build.gradle +++ b/forge-1.21.10/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -37,7 +41,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.21.10" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.21.10" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.21.11/build.gradle b/forge-1.21.11/build.gradle index 854ab09c8..63c53ed27 100644 --- a/forge-1.21.11/build.gradle +++ b/forge-1.21.11/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -37,7 +41,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.21.11" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.21.11" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-1.21.6/build.gradle b/forge-1.21.6/build.gradle index 0e002414a..d27371028 100644 --- a/forge-1.21.6/build.gradle +++ b/forge-1.21.6/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://files.minecraftforge.net/maven' } - jcenter() mavenCentral() } dependencies { @@ -18,7 +17,12 @@ eclipse { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21) // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('21') + targetCompatibility = JavaVersion.toVersion('21') +} +compileJava.sourceCompatibility = '21' +compileJava.targetCompatibility = '21' // Need this here so eclipse task generates correctly. println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) @@ -36,7 +40,7 @@ minecraft { } } -project.archivesBaseName = "${project.archivesBaseName}-forge-1.21.6" +project.base.archivesName = "${project.base.archivesName.get()}-forge-1.21.6" dependencies { implementation project(path: ":DynmapCore", configuration: "shadow") diff --git a/forge-build/build.gradle b/forge-build/build.gradle new file mode 100644 index 000000000..7f3d42471 --- /dev/null +++ b/forge-build/build.gradle @@ -0,0 +1,70 @@ +// Workaround for Shadow not supporting Java 19 classes. +// Remove this once Shadow updates. +// See: https://github.com/johnrengelman/shadow/pull/770 +// See: https://discord.com/channels/722722769950998560/793019909055578113/978939925061857315 +buildscript { + configurations.all { + resolutionStrategy { + force("org.ow2.asm:asm:9.5") + force("org.ow2.asm:asm-commons:9.5") + } + } +} + +plugins { + id "io.github.goooler.shadow" version "8.1.7" + id 'java' + id 'maven-publish' +} + +apply plugin: 'eclipse' + +eclipse { + project { + name = "Dynmap(Forge)" + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://libraries.minecraft.net/' } + maven { url "https://oss.sonatype.org/content/repositories/releases" } + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://repo.mikeprimm.com" } + maven { url "https://repo.maven.apache.org/maven2" } + maven { url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" } + maven { url "https://repo.codemc.org/repository/maven-public/" } + } + + apply plugin: 'java' + + group = 'us.dynmap' + version = '3.9-SNAPSHOT' + +} + +class Globals { + String buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" +} +ext { + globals = new Globals() +} + +subprojects { + apply plugin: "io.github.goooler.shadow" + apply plugin: 'java' + apply plugin: 'maven-publish' + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + } +} + +clean { + delete "target" +} diff --git a/forge-build/gradle.properties b/forge-build/gradle.properties new file mode 100644 index 000000000..1f4688034 --- /dev/null +++ b/forge-build/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +org.gradle.daemon=false +org.gradle.parallel=false diff --git a/forge-build/gradle/wrapper/gradle-wrapper.jar b/forge-build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/forge-build/gradle/wrapper/gradle-wrapper.jar differ diff --git a/forge-build/gradle/wrapper/gradle-wrapper.properties b/forge-build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca025c83a --- /dev/null +++ b/forge-build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/forge-build/gradlew b/forge-build/gradlew new file mode 100644 index 000000000..1aa94a426 --- /dev/null +++ b/forge-build/gradlew @@ -0,0 +1,249 @@ +#!/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. +# + +############################################################################## +# +# 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/subprojects/plugins/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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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, 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" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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" "$@" diff --git a/forge-build/gradlew.bat b/forge-build/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/forge-build/gradlew.bat @@ -0,0 +1,92 @@ +@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 + +@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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +: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 diff --git a/forge-build/settings.gradle b/forge-build/settings.gradle new file mode 100644 index 000000000..676f65d48 --- /dev/null +++ b/forge-build/settings.gradle @@ -0,0 +1,29 @@ +rootProject.name = 'dynmap-forge' + +include ':DynmapCoreAPI' +include ':DynmapCore' +include ':dynmap-api' +include ':forge-1.14.4' +include ':forge-1.15.2' +include ':forge-1.16.5' +include ':forge-1.17.1' +include ':forge-1.18.2' +include ':forge-1.19.3' +include ':forge-1.20.6' +include ':forge-1.21.6' +include ':forge-1.21.10' +include ':forge-1.21.11' + +project(':DynmapCoreAPI').projectDir = "$rootDir/../DynmapCoreAPI" as File +project(':DynmapCore').projectDir = "$rootDir/../DynmapCore" as File +project(':dynmap-api').projectDir = "$rootDir/../dynmap-api" as File +project(':forge-1.14.4').projectDir = "$rootDir/../forge-1.14.4" as File +project(':forge-1.15.2').projectDir = "$rootDir/../forge-1.15.2" as File +project(':forge-1.16.5').projectDir = "$rootDir/../forge-1.16.5" as File +project(':forge-1.17.1').projectDir = "$rootDir/../forge-1.17.1" as File +project(':forge-1.18.2').projectDir = "$rootDir/../forge-1.18.2" as File +project(':forge-1.19.3').projectDir = "$rootDir/../forge-1.19.3" as File +project(':forge-1.20.6').projectDir = "$rootDir/../forge-1.20.6" as File +project(':forge-1.21.6').projectDir = "$rootDir/../forge-1.21.6" as File +project(':forge-1.21.10').projectDir = "$rootDir/../forge-1.21.10" as File +project(':forge-1.21.11').projectDir = "$rootDir/../forge-1.21.11" as File diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c83a..5dd3c0121 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 3429534f0..51b4b7662 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include ':bukkit-helper-121-5' include ':bukkit-helper-121-6' include ':bukkit-helper-121-10' include ':bukkit-helper-121-11' +include ':bukkit-helper-26-2' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' @@ -40,6 +41,7 @@ include ':DynmapCoreAPI' include ':fabric-1.21.6' include ':fabric-1.21.9-10' include ':fabric-1.21.11' +include ':fabric-26.2' include ':fabric-1.20.6' include ':fabric-1.19.4' include ':fabric-1.18.2' @@ -47,16 +49,10 @@ include ':fabric-1.17.1' include ':fabric-1.16.4' include ':fabric-1.15.2' include ':fabric-1.14.4' -include ':forge-1.21.11' -include ':forge-1.21.10' -include ':forge-1.21.6' -include ':forge-1.20.6' -include ':forge-1.19.3' -include ':forge-1.18.2' -include ':forge-1.17.1' -include ':forge-1.16.5' -include ':forge-1.15.2' -include ':forge-1.14.4' +// forge-1.14.4 through forge-1.21.11 build via ForgeGradle, which does not support Gradle 9 +// ("Versions Gradle 9.0 and newer are not supported yet") - they now live in their own +// Gradle-8.14 build under forge-build/ (see forge-build/settings.gradle), the same pattern +// already used for forge-1.12.2 in oldgradle/oldbuilds. project(':spigot').projectDir = "$rootDir/spigot" as File project(':bukkit-helper-113-2').projectDir = "$rootDir/bukkit-helper-113-2" as File @@ -83,6 +79,7 @@ project(':bukkit-helper-121-5').projectDir = "$rootDir/bukkit-helper-121-5" as F project(':bukkit-helper-121-6').projectDir = "$rootDir/bukkit-helper-121-6" as File project(':bukkit-helper-121-10').projectDir = "$rootDir/bukkit-helper-121-10" as File project(':bukkit-helper-121-11').projectDir = "$rootDir/bukkit-helper-121-11" as File +project(':bukkit-helper-26-2').projectDir = "$rootDir/bukkit-helper-26-2" as File project(':bukkit-helper').projectDir = "$rootDir/bukkit-helper" as File project(':dynmap-api').projectDir = "$rootDir/dynmap-api" as File project(':DynmapCore').projectDir = "$rootDir/DynmapCore" as File @@ -90,6 +87,7 @@ project(':DynmapCoreAPI').projectDir = "$rootDir/DynmapCoreAPI" as File project(':fabric-1.21.6').projectDir = "$rootDir/fabric-1.21.6" as File project(':fabric-1.21.9-10').projectDir = "$rootDir/fabric-1.21.9-10" as File project(':fabric-1.21.11').projectDir = "$rootDir/fabric-1.21.11" as File +project(':fabric-26.2').projectDir = "$rootDir/fabric-26.2" as File project(':fabric-1.20.6').projectDir = "$rootDir/fabric-1.20.6" as File project(':fabric-1.19.4').projectDir = "$rootDir/fabric-1.19.4" as File project(':fabric-1.18.2').projectDir = "$rootDir/fabric-1.18.2" as File @@ -97,14 +95,4 @@ project(':fabric-1.17.1').projectDir = "$rootDir/fabric-1.17.1" as File project(':fabric-1.16.4').projectDir = "$rootDir/fabric-1.16.4" as File project(':fabric-1.15.2').projectDir = "$rootDir/fabric-1.15.2" as File project(':fabric-1.14.4').projectDir = "$rootDir/fabric-1.14.4" as File -project(':forge-1.21.11').projectDir = "$rootDir/forge-1.21.11" as File -project(':forge-1.21.10').projectDir = "$rootDir/forge-1.21.10" as File -project(':forge-1.21.6').projectDir = "$rootDir/forge-1.21.6" as File -project(':forge-1.20.6').projectDir = "$rootDir/forge-1.20.6" as File -project(':forge-1.19.3').projectDir = "$rootDir/forge-1.19.3" as File -project(':forge-1.18.2').projectDir = "$rootDir/forge-1.18.2" as File -project(':forge-1.17.1').projectDir = "$rootDir/forge-1.17.1" as File -project(':forge-1.16.5').projectDir = "$rootDir/forge-1.16.5" as File -project(':forge-1.15.2').projectDir = "$rootDir/forge-1.15.2" as File -project(':forge-1.14.4').projectDir = "$rootDir/forge-1.14.4" as File diff --git a/spigot/build.gradle b/spigot/build.gradle index 9fa548e1d..afdbbb8d7 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -16,7 +16,17 @@ repositories { } } -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +java { + sourceCompatibility = JavaVersion.toVersion('1.8') + targetCompatibility = JavaVersion.toVersion('1.8') + // spigot never links against bukkit-helper-* at compile time - it only loads whichever one + // matches the running server via reflection (see Helper.java) - so Gradle's automatic + // "consumer JVM version must be >= dependency's JVM version" check is a false positive here; + // several bukkit-helper-* modules (117+) target newer JDKs than spigot's own JDK 8 baseline. + disableAutoTargetJvm() +} +compileJava.sourceCompatibility = '1.8' +compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. dependencies { implementation('org.bukkit:bukkit:1.10.2-R0.1-SNAPSHOT') { transitive = false } @@ -109,6 +119,9 @@ dependencies { implementation(project(':bukkit-helper-121-11')) { transitive = false } + implementation(project(':bukkit-helper-26-2')) { + transitive = false + } } processResources { @@ -128,7 +141,8 @@ jar { shadowJar { dependencies { - include(dependency('org.bstats::')) + include(dependency('org.bstats:bstats-bukkit:.*')) + include(dependency('org.bstats:bstats-base:.*')) include(dependency(':dynmap-api')) include(dependency(":DynmapCore")) include(dependency(':bukkit-helper')) @@ -156,6 +170,7 @@ shadowJar { include(dependency(':bukkit-helper-121-6')) include(dependency(':bukkit-helper-121-10')) include(dependency(':bukkit-helper-121-11')) + include(dependency(':bukkit-helper-26-2')) } relocate('org.bstats', 'org.dynmap.bstats') destinationDirectory = file '../target' @@ -164,7 +179,7 @@ shadowJar { } shadowJar.doLast { task -> - ant.checksum file: task.archivePath + ant.checksum file: task.archiveFile.get().asFile } artifacts { diff --git a/spigot/src/main/java/org/dynmap/bukkit/Helper.java b/spigot/src/main/java/org/dynmap/bukkit/Helper.java index 26cc72e7f..d205e5c95 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -1,6 +1,7 @@ package org.dynmap.bukkit; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import org.bukkit.Bukkit; import org.dynmap.Log; @@ -18,6 +19,18 @@ private static BukkitVersionHelper loadVersionHelper(String classname) { return null; } } + + // Server#getMinecraftVersion() was added long after the ancient Bukkit API (1.10.2) that this + // module compiles against, so it isn't a compile-time symbol here - called via reflection instead. + private static String getMinecraftVersionReflective() { + try { + Method m = Bukkit.getServer().getClass().getMethod("getMinecraftVersion"); + return (String) m.invoke(Bukkit.getServer()); + } catch (Exception x) { + Log.severe("Error calling Server#getMinecraftVersion()", x); + return null; + } + } public static final BukkitVersionHelper getHelper() { if (BukkitVersionHelper.helper == null) { String v = Bukkit.getServer().getVersion(); @@ -117,6 +130,23 @@ else if (v.contains("(MC: 1.14)") || v.contains("(MC: 1.14.1)") || v.contains("( else if (v.contains("(MC: 1.13.2)")) { BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v113_2.BukkitVersionHelperSpigot113_2"); } + // Minecraft 26.1+ switched to year.release version numbers - the "(MC: x)" substring is + // still present (confirmed on a real 26.2 server: "26.2-62-... (MC: 26.2)"), just with + // the new-style version string inside it instead of the old "1.x.y" one. + else if (v.contains("(MC: 26.2)")) { + BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v26_2.BukkitVersionHelperSpigot26_2"); + } + // Fallback in case some future server reports the Minecraft version differently (e.g. no + // "(MC: x)" substring at all) - try the dedicated accessor before giving up. + else if (!v.contains("(MC:")) { + String mcver = getMinecraftVersionReflective(); + if ("26.2".equals(mcver)) { + BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v26_2.BukkitVersionHelperSpigot26_2"); + } + else { + Log.severe("Unsupported Minecraft version (" + mcver + ") - Dynmap does not yet have a bukkit-helper for this version."); + } + } else { BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.BukkitVersionHelperCB"); }