Compare commits

...

No commits in common. "neoforge-1.21.1" and "main" have entirely different histories.

4687 changed files with 449565 additions and 490 deletions

View file

@ -0,0 +1,40 @@
# .forgejo/workflows/gitleaks.yml
#
# Sulkta canonical gitleaks workflow. Drop a copy into every public repo at
# `.forgejo/workflows/gitleaks.yml` after the Forgejo act_runner is registered
# (task #295).
#
# Pairs with the pre-receive hook installed on every bare repo — that one is
# the strict enforcement layer (rejects the push); this one provides the
# per-PR red ✗ that branch-protection rules can require before merge.
#
# Layer 1 (this workflow): visible per-PR status, can be a required check.
# Layer 2 (pre-receive hook): strict enforcement at the server.
# Layer 3 (johnny5 cron sweep): nightly full-history sweep across all repos.
name: gitleaks
on:
push:
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Full history — gitleaks needs depth to scan a commit range.
fetch-depth: 0
- name: install gitleaks
run: |
curl -sSL -o gl.tar.gz \
https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz
tar xzf gl.tar.gz gitleaks
chmod +x gitleaks
./gitleaks version
- name: scan
run: |
./gitleaks detect --source . --no-banner --redact --verbose

24
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,24 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: Bug Report
assignees: ''
---
> template is **bold**
> sample data is *italicized*
**Issue Description:** *Dynmap sample issue description. This description would include as much and as little detail necessary for us to understand the issue in its entirety.*
* **Dynmap Version:** *dynmap-version*
* **Server Version:** *forge 1.16 build x, or paper 1.15.2 build x*
* **Pastebin of Configuration.txt:** *https://pastebin.com/5SETL3z2*
* **Server Host (if applicable):** *McProHosting, Shockbyte, Selfhosted, etc.*
* **Pastebin of crashlogs or other relevant logs:** *https://pastebin.com/crashcausedbydynmap*
* **Other Relevant Data/Screenshots:** *I'm using this texture pack and these plugins and my map is from alpha .8*
* **Steps to Replicate:** *Please be clear in this section so we can replicate your issue*
[ ] *I have looked at all other issues and this is not a duplicate*
[ ] *I have been able to replicate this*

View file

@ -0,0 +1,15 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Feature Request
assignees: ''
---
> template is **bold**
> sample data is *italicized*
**Feature Description:** *Dynmap sample feature request description. This description would include as much and as little detail necessary for us to understand the feature in its entirety.*
* **Additional context:** *Please add any additional information you feel will help us understand the feature*

62
.github/workflows/gibberish.txt vendored Normal file
View file

@ -0,0 +1,62 @@
Subreddit
blockscanner
dynmap
Dynmap's
APIs
APL
Dynmap
Dynmap's
DynmapCore
DynmapCoreAPI
JRuby
macOS
Minecraft
PRs
PaperMC
WorldGuard
www
reflow
runtime
theose
DynampCore
DynmapCore
DynmapCoreAPI
Reddit
api
Bukkit
mikeprimm
michaelprimm
repo
https
Fi
Ki
Patreon
fi
ko
patreon
README
Gradle
gradlew
gradle
oldgradle
Curseforge
SpigotMC
PaperMC
backends
Minecraft
WorldGuard
reflow
PRs
JRuby
Primm
reddit
subreddit
gg
pqBpw
JDBC
JDK
ForgeGradle
Kosma
Kosma's
DEV
Modrinth

14
.github/workflows/spellcheck.yaml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Checking for spelling errors
on:
push:
jobs:
spellcheck:
name: rojopolis/spellcheck
runs-on: ubuntu-20.04
steps:
- name: actions/checkout
uses: actions/checkout@v2
- name: rojopolis/spellcheck - actually doing something
uses: rojopolis/spellcheck-github-actions@0.29.0

41
.gitignore vendored
View file

@ -1,3 +1,40 @@
build/
.gradle/
# Eclipse stuff
.classpath
.project
.settings
# netbeans
/nbproject
# idea
.idea/
*.launch
# we use maven!
/build.xml
# maven
/target
# vim
.*.sw[a-p]
# various other potential build files
/build
/bin
/dist
/manifest.mf
/run
# Mac filesystem dust
/.DS_Store
/dependency-reduced-pom.xml
*.log
/.gradle
/fabric-1.16.1_client.launch
/fabric-1.16.1_server.launch
/fabric-1.16.2_client.launch
/fabric-1.16.2_server.launch

25
.spellcheck.yaml Normal file
View file

@ -0,0 +1,25 @@
spellchecker: aspell
matrix:
- name: Markdown
aspell:
lang: en
dictionary:
wordlists:
- .github/workflows/gibberish.txt
encoding: utf-8
pipeline:
- pyspelling.filters.markdown:
markdown_extensions:
- pymdownx.extra:
- pyspelling.filters.html:
comments: true
attributes:
- title
- alt
ignores:
- ':matches(code, pre)'
- 'code'
- 'pre'
sources:
- '**/*.md'
default_encoding: utf-8

4
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "automatic",
"java.compile.nullAnalysis.mode": "automatic"
}

View file

@ -1,87 +0,0 @@
# Changelog — Dynmap NeoForge 1.21.1 Port
All changes relative to the upstream `neoforge-1.20.6` module.
---
## [2026-03-07] — Final clean state (pre-PR)
### Reverted
- `touchChunk()` helper method extraction — inlined back to match 1.20.6 structure
- Was purely cosmetic; upstream CONTRIBUTING.md prohibits refactoring in PRs
- No behavior change, no runtime impact
### Fixed — whitespace
- `ForgeMapChunkCache.java` — formatting-only diffs corrected to match 1.20.6 style
---
## [2026-03-07] — canOcclude() deadlock fix
### Changed
- `DynmapPlugin.initializeBlockStates()` (lines ~248, ~259):
- `isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)``canOcclude()`
- `propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)``isAir()`
- `isSolid()``canOcclude()`
### Why
`isSolidRender()` / `propagatesSkylightDown()` / `isSolid()` all trigger a Guava
`LoadingCache` lookup in ModernFix's `reduce_blockstate_cache_rebuilds` mixin. During
world load with modernfix + ferritecore (present in ATM10), this causes a
`BlockState$Cache.<init>` deadlock — lazy/deferred block state initialization enters
an infinite loop in VoxelShape calculation for certain complex mod block states.
The watchdog kills the server after ~60 seconds.
`canOcclude()` and `isAir()` read precomputed booleans directly from the block state
with no cache involvement. Functionally equivalent for Dynmap's purposes, safe with
all mods including modernfix and ferritecore.
### Test result
- ✅ ATM10 5.5, 445 mods, NeoForge 21.1.219 — 3/3 clean boots
- ✅ `[Dynmap] version 3.7-SNAPSHOT-Dev is enabled`
- ✅ 17 worlds loaded, web server started on 0.0.0.0:8123
---
## [2026-03-07] — Runtime fixes + build improvements
### Fixed — API changes (1.21.1)
- `DynmapPlugin`: `ServerTickEvent` listener updated to `ServerTickEvent.Post`
- NeoForge 21.x made `ServerTickEvent` abstract; registering the base class no longer works
- `DynmapPlugin.initializeBlockStates()`: replaced `isSolidRender(null, ...)` with `isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)`
- 1.21.1 actually uses the BlockGetter parameter; passing null causes NPE
- (Superseded by canOcclude() fix above — kept here for history)
- `DynmapPlugin`: `getLastAvailable()``getLatestChunk()` — API renamed
- `DynmapPlugin`: `getStatus()``getPersistedStatus()` — API renamed
- `DynmapPlugin`: `ChatHandler` inner class → direct method with `addListener()`
- NeoForge 1.21.1 changed event registration; inner class pattern no longer supported
- `NBT.java`: `contains()` reimplemented with manual type checking (behavior changed in 1.21.1)
- `NBT.java`: `getAsString()` null safety added (behavior changed in 1.21.1)
### Fixed — Build
- `build.gradle`: userdev plugin 7.0.133 → 7.1.20 (NeoForge 21.1.x requirement)
- `build.gradle`: NeoForge dep 20.6.62-beta → 21.1.219
- `gradle-wrapper.properties`: 8.7 → 8.14 (required for userdev 7.1.20)
- All `fabric-*/build.gradle`: loom 1.6.11 → 1.8.13 (required for Gradle 8.14 compat)
- `neoforge.mods.toml`: loader/version ranges updated for 1.21.1
- Access transformer: SRG names → mojmap names
- Heap caps: Gradle daemon `-Xmx2g`, forked javac `-Xmx3g`
### ⚠️ Known discrepancy (from earlier commit message)
- An early commit message claimed `visibleChunkMap` was replaced with `getChunks()`
- **This was inaccurate.** `visibleChunkMap` is still in the code and works fine in NeoForge 1.21.1
---
## [2026-03-07] — Initial 1.21.1 compile fix pass
### Fixed — Compile errors (43 total)
- All compile errors resolved from `neoforge-1.20.6``neoforge-1.21.1` baseline:
- `net.minecraftforge.*``net.neoforged.*` (full package migration)
- `MinecraftForge.EVENT_BUS``NeoForge.EVENT_BUS`
- Access transformer updated for 1.21.1 private field patterns
---
## Format note
Entries are dated by session (not release), since this is a port-in-progress.

107
CLAUDE.md Normal file
View file

@ -0,0 +1,107 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Dynmap is a dynamic web mapping plugin/mod for Minecraft servers. It's a multi-platform project supporting Spigot/PaperMC, Forge, and Fabric across multiple Minecraft versions (1.12.2 - 1.21.x).
## Build Commands
```bash
# Build all platforms (requires JDK 21 as default)
./gradlew setup build
# Build outputs go to /target directory
# Build specific module (for faster iteration, but NOT for PR submissions)
./gradlew :DynmapCore:build
# Run unit tests (DynmapCore only — JUnit 4)
./gradlew :DynmapCore:test
# Forge 1.12.2 (requires JDK 8 - set JAVA_HOME accordingly)
cd oldgradle
./gradlew setup build
```
**JDK Requirements:**
- Default: JDK 21
- 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+)
**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
## Architecture
### Module Structure
**Core Shared Modules:**
- `DynmapCoreAPI/` - Stable public API for external plugins/mods (markers, mod support, rendering). Published to `repo.mikeprimm.com`. The `org.dynmap.renderer` package here defines `DynmapBlockState` — the central block state abstraction used everywhere.
- `DynmapCore/` - Internal shared implementation (NOT stable - subject to breaking changes)
- `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/`
### Dependency Flow
```
External Plugins/Mods
DynmapCoreAPI (stable, published to repo.mikeprimm.com)
DynmapCore (internal, unstable)
Platform-specific modules (Spigot, Fabric, Forge)
```
### Key Components in DynmapCore
- `DynmapCore.java` — Main coordination hub (~3,100 lines); bootstrapped by each platform
- `MapManager.java` — Tile rendering orchestration; owns the render thread pool and `FullWorldRenderState` queue
- `hdmap/` — HD map rendering pipeline:
- `IsoHDPerspective` — Isometric raytrace engine (the hot rendering path)
- `HDBlockModels` / `HDScaledBlockModels` — Block geometry (patch/volumetric/scaled models)
- `TexturePack` / `TexturePackLoader` — Texture resolution from resource packs
- `hdmap/renderer/` — Custom block renderers (stairs, fences, doors, etc.) implementing `CustomRenderer`
- Shaders (`DefaultHDShader`, `CaveHDShader`, `TopoHDShader`, etc.) — post-process pixel color
- Lighting (`DefaultHDLighting`, `ShadowHDLighting`, etc.) — light level calculation
- `storage/` — Storage backends (FileTree, MySQL, MariaDB, PostgreSQL, SQLite, MSSQL, AWS S3)
- `web/` — Embedded Jetty 9 server with custom HTTP routing (no standard servlet container)
- `markers/impl/` — Full marker system implementation; public interface is in `DynmapCoreAPI`
- `utils/MapChunkCache` + `utils/MapIterator` — Abstract interfaces that each platform implements to feed world data into the renderer
### Platform Integration Pattern
Each platform module (Spigot `bukkit-helper-*`, Fabric, Forge) must implement:
- `MapChunkCache` — Loads and caches chunk data for a tile's required chunks
- `MapIterator` — Block-by-block iteration over the loaded chunk cache
- A platform entry point (e.g., `DynmapPlugin` for Spigot) that bootstraps `DynmapCore`
The `bukkit-helper-*` modules contain version-specific NMS code; `spigot/` delegates to the appropriate helper at runtime via reflection.
## Testing
Unit tests exist in `DynmapCore/src/test/` (JUnit 4) covering `Matrix3D`, `Vector3D`, `IpAddressMatcher`, `DynIntHashMap`, and `BufferInputStream`. Run with `./gradlew :DynmapCore:test`.
Full verification requires:
1. Building all platforms: `./gradlew setup build` AND `cd oldgradle && ./gradlew setup build`
2. Manual testing on target Minecraft server platforms
## Critical Contribution Rules
**PRs must build and test on ALL platforms including oldgradle. Changes to DynmapCore/DynmapCoreAPI require testing on all platforms.**
- **Java 8 compatibility required** — Code must compile and run on Java 8
- **Java only** — No Kotlin, Scala, or other JVM languages
- **No dependency updates** — Library versions are tied to platform compatibility
- **No platform-specific code** — Must work on Windows, Linux (x86/ARM), macOS, Docker
- **Small PRs only** — One feature per PR, no style/formatting changes
- **No mod-specific code** — Use Dynmap APIs instead; external mods should depend on DynmapCoreAPI
- **Apache License v2** — All code must be compatible
- **DynmapCoreAPI is the only stable API** — Do not add external dependencies on DynmapCore internals

76
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,76 @@
## Do you need support?
Details about support for the project can be found here on the Wiki. You may also wish to use the [Subreddit](https://reddit.com/r/Dynmap/) or the [Discord](https://discord.gg/52pqBpw) to get support. Templates for asking support can be found in each location.
## Have you found a bug?
Before reporting a bug or issue, please make sure you can replicate the issue and that the issue is directly related to the main dynmap branch (not one of the forks like dynmap-essentials, dynmap-blockscanner, etc) If so, please submit bug reports **ONLY TO THIS GITHUB** with the title `[BUG REPORT] Bug Report Title`
## Contributing to Dynmap's Code?
The Dynmap team welcomes Pull Requests with fixes, new features, and new platform support. That said, the following rules apply:
- Ultimately, we reserve the right to accept or deny a PR for any reason: fact is, by accepting it, we're also accepting any of the problems with supporting it,
explaining it to users, and fixing current and future problems - if we don't think the PR is of value consistent with that cost, we'll probably not accept it.
- All PRs should be as small as they can be to accomplish the feature or fix being supplied. To that end:
- Do not lump multiple features into one PR - you'll be asked to split them up before they will be reviewed or accepted
- Do not make style changes, reflow code, pretty printing, or otherwise make formatting-only code changes. This makes the PR excessively large,
creating changes to be reviewed that don't actually do anything (but we have to review them to be sure they aren't being used to disguise security
compromises or other malicious code), and they create problems with the MANY people who fork Dynmap for the sake of doing PRs or their own private
custom builds - since all theose modified lines create merge conflicts - once again, with no actual function having been accomplished. If we decide
the code needs to be 'prettied up', it'll be done by the Dynmap team.
- Do not make changes to core code (anything in DynmapCore or DynmapCoreAPI) unless you're ready to build and test it on all supported platforms. Code that
breaks building of ANY supported platform will be rejected.
- Likewise, any Spigot related changes are expected to function correctly on all supported Spigot and PaperMC versions (currently 1.10.2 through 1.16.1).
- Do not include any code that involves platform specific native libraries or command line behaviors. Dynmap supports 32-bit and 64-bit, Windows, lots of
Linux versions (both x86 and ARM), MacOS, being used in Docker environments, and more - this is all about staying as 'pure Java' as the Minecraft server itself
is. If your PR includes platform specific dependencies that are not coded to handle working on all the above platforms properly, the PR will be rejected.
- Dynmap's code is Apache Public License v2 - do not include any code that is not compatible with this license. By contributing code, you are agreeing to
that code being subject to the APL v2.
- Do not include any code that unconditionally adds to Dynmap's hosting requirements - for example, support for a database can be added, but the use of the
database (which likely depends on a database server being deployed and configured by the user) cannot become an unconditional requirement in order to run
Dynmap. Features can add the option to exploit new or additional technologies, but cannot add unconditionally to the minimum requirements on the supported
platforms (which is what is needed to run the corresponding MC server, plus the Dynmap plugin or mod)
- Dynmap is built and supports running on Java 8 - it can run on newer versions, but any contributed code and dependencies MUST support being compiled and run
using just Java 8.
- Don't introduce other language dependencies - Java only: no Kotlin, Scala, JRuby, whatever. They just add runtime dependencies that most of the platforms lack,
and language skills above and beyond the Java language requirements the code base already mandates, which just creates obstacles to other people contributing.
- Similarly, do not update existing libraries and dependencies - these are often tied to the versions on various platforms, and updates will likely break runtime
- Do not include code specific to other plugins or mods. Dynmap has APIs for the purpose of avoiding the problem of working with other mods - there are many
'Dynmap-XXX' mods and plugins which use the APIs to provide support for other mods and plugins (WorldGuard, Nucleus, Citizens, dozens of others). Maintaining
interfaces in Dynmap particular to dozens of mods on multiple versions of multiple platforms is unmanageable, so we don't do it. The ONLY exception currently
are security mods - although, even for those, leverage of platform-standard security interfaces is always preferred (e.g. Sponge or Bukkit standard permissions)
## Porting, Supporting Other Platforms, Customized Dynmap Builds
While Dynmap is open source, subject to the Apache Public License, v2, the Dynmap team does have specific policies and requirements for anyone that would
use the code here for anything except building contributions submitted back to this code base as Pull Requests (which is the only process by which code is accepted and can become part of a release supported by the Dynmap team). Other authorized uses include:
- Building custom version of Dynmap for use on a personal or on a specific server, so long as this version is NOT distributed to other parties.
The modifying team agrees to not pursue support from the Dynmap team for this modified private version, but is otherwise not required to share the
modified source code - though doing so is encouraged.
- Building a modified version of Dynmap for otherwise unsupported platforms: in this event, the modified version MUST be for a platform or version
not yet (or no longer) supported by the Dynmap team. If the Dynmap team comes to support this platform or version, the modifying team must agree to
cease distribution of the unofficial version, unless otherwise authorized to continue doing so. Further:
- The team distributing the modified version must cite the origin of the Dynmap code, but must also clearly indicate that the version is NOT supported by
nor endorsed by the Dynmap team, and that ALL support should be directed through the team providing the modified version.
- Any modified version CANNOT be monetized or otherwise charged for, under any circumstances, nor can redistribution of it be limited or restricted.
- The modified code must continue to be Apache Public License v2, with no additional conditions or restrictions, including full public availability of the
modified source code.
- Any code from Dynmap used in such versions should be built from an appropriate fork, as DynampCore and other components (other than DynmapCoreAPI and
dynmap-api) are subject to breaking changes at any time, and the support messages in DynmapCore MUST be modified to refer to the supporting team (or, at
least, removed). The modified version should NOT refer to the Dynmap Discord nor to /r/Dynmap on Reddit for support. in any case.
- Any bugs or issues opened in conjunction with the use of the modified version on this repository will be closed without comment.
Additions of new functions, including new platform support, in this official Dynmap code base MUST be fully contained within the PRs submitted to this
repository. Further, it is always expected than any updates will be built and tested across all relevant platforms - meaning any chances to shared code
components (DynmapCore, DynmapCoreAPI) MUST be successfully built and tested on ALL supported platforms (Forge, Spigot, etc). Changes which break
supported platforms will be rejected.
The only interfaces published and maintained as 'stable' are the interfaces of the DynmapCoreAPI (cross platform) and dynmap-api (Bukkit/spigot specific)
libraries. All other components are NOT libraries - DynmapCore, in particular, is a shared code component across the various platforms, but is subject to
breaking changes without warning or consideration - any use of DynmapCore interfaces by code outside this repository is NOT supported, and will likely
result in breaking of such consuming code without warning and without apology. DynmapCore is an internal shared code component, not a library - please
treat it accordingly.
Plugins or mods using the published APIs - DynmapCoreAPI (for all platforms) or dynmap-api (only for Spigot/Bukkit) - may access these components as
'compile' dependencies: DO NOT INTEGRATE THEM INTO YOUR PLUGIN - this will break Dynmap and/or other plugins when these interfaces are updated or
expanded. These libraries are published at https://repo.mikeprimm.com and will be updated each official release.
## Want to support the dynmap team?
I've set up a coffee-fund jar (I believe in the theory that software developers are machines that turn caffeine into code), for anyone who wants to throw in some tips! I've got a Patreon here - https://www.patreon.com/dynmap, and for folks just looking to for a one-time coffee buy, hit my Ko-Fi at https://ko-fi.com/michaelprimm !

2
DynmapCore/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/build/
/bin/

100
DynmapCore/build.gradle Normal file
View file

@ -0,0 +1,100 @@
description = "DynmapCore"
apply plugin: 'eclipse'
eclipse {
project {
name = "Dynmap(DynmapCore)"
}
}
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
dependencies {
implementation project(':DynmapCoreAPI')
implementation 'javax.servlet:javax.servlet-api:3.1'
implementation'org.eclipse.jetty:jetty-server:9.4.26.v20200117'
implementation 'org.eclipse.jetty:jetty-servlet:9.4.26.v20200117'
implementation 'com.googlecode.json-simple:json-simple:1.1.1'
implementation 'org.yaml:snakeyaml:1.23' // DON'T UPDATE - NEWER ONE TRIPS ON WINDOWS ENCODED FILES
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20180219.1'
implementation 'org.postgresql:postgresql:42.2.18'
implementation 'io.github.linktosriram.s3lite:core:0.0.2-SNAPSHOT'
implementation 'io.github.linktosriram.s3lite:api:0.0.2-SNAPSHOT'
implementation 'io.github.linktosriram.s3lite:http-client-url-connection:0.0.2-SNAPSHOT'
implementation 'io.github.linktosriram.s3lite:http-client-spi:0.0.2-SNAPSHOT'
implementation 'io.github.linktosriram.s3lite:util:0.0.2-SNAPSHOT'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:3.0.1'
implementation 'com.sun.xml.bind:jaxb-impl:3.0.0'
// Test dependencies (Java 8 compatible versions)
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.assertj:assertj-core:3.24.2'
}
test {
useJUnit()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
}
}
processResources {
// replace stuff in mcmod.info, nothing else
filesMatching([
'core.yml',
'lightings.txt',
'perspectives.txt',
'shaders.txt',
'extracted/web/version.js',
'extracted/web/index.html',
'extracted/web/login.html',
]) {
// replace version and mcversion
expand(
buildnumber: project.parent.ext.globals.buildNumber,
version: project.version
)
}
}
jar {
archiveClassifier = 'unshaded'
}
shadowJar {
dependencies {
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('org.eclipse.jetty.orbit:javax.servlet:'))
include(dependency('org.postgresql:postgresql:'))
include(dependency('io.github.linktosriram.s3lite:core:'))
include(dependency('io.github.linktosriram.s3lite:api:'))
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(':DynmapCoreAPI'))
exclude("META-INF/maven/**")
exclude("META-INF/services/**")
}
relocate('org.json.simple', 'org.dynmap.json.simple')
relocate('org.yaml.snakeyaml', 'org.dynmap.snakeyaml')
relocate('org.eclipse.jetty', 'org.dynmap.jetty')
relocate('org.owasp.html', 'org.dynmap.org.owasp.html')
relocate('javax.servlet', 'org.dynmap.javax.servlet' )
relocate('org.postgresql', 'org.dynmap.org.postgresql')
relocate('io.github.linktosriram.s3lite', 'org.dynmap.s3lite')
destinationDirectory = file '../target'
archiveClassifier = ''
}
artifacts {
archives shadowJar
}

1
DynmapCore/src/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,162 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
public class AsynchronousQueue<T> {
private Object lock = new Object();
private Thread thread;
private LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<T>();
private Set<T> set = new HashSet<T>();
private Handler<T> handler;
private int dequeueTime;
private int accelDequeueTime;
public int accelDequeueThresh;
private int pendingcnt;
private int pendinglimit;
private boolean normalprio;
public AsynchronousQueue(Handler<T> handler, int dequeueTime, int accelDequeueThresh, int accelDequeueTime, int pendinglimit, boolean normalprio) {
this.handler = handler;
this.dequeueTime = dequeueTime;
this.accelDequeueTime = accelDequeueTime;
this.accelDequeueThresh = accelDequeueThresh;
if(pendinglimit < 1) pendinglimit = 1;
this.pendinglimit = pendinglimit;
this.normalprio = normalprio;
}
public boolean push(T t) {
synchronized (lock) {
if (!set.add(t)) {
return false;
}
}
queue.offer(t);
return true;
}
private T pop() {
try {
T t = queue.take();
synchronized (lock) {
set.remove(t);
}
return t;
} catch (InterruptedException ix) {
return null;
}
}
public boolean remove(T t) {
synchronized (lock) {
if (set.remove(t)) {
queue.remove(t);
return true;
}
}
return false;
}
public int size() {
return set.size();
}
public List<T> popAll() {
List<T> s;
synchronized(lock) {
s = new ArrayList<T>(queue);
queue.clear();
set.clear();
}
return s;
}
public void start() {
synchronized (lock) {
thread = new Thread(new Runnable() {
@Override
public void run() {
running();
}
});
thread.setDaemon(true);
thread.start();
try {
if(!normalprio)
thread.setPriority(Thread.MIN_PRIORITY);
} catch (SecurityException e) {
Log.info("Failed to set minimum priority for worker thread!");
}
}
}
public void stop() {
synchronized (lock) {
if (thread == null)
return;
Thread oldThread = thread;
thread = null;
Log.info("Stopping map renderer...");
oldThread.interrupt();
try {
oldThread.join(1000);
} catch (InterruptedException e) {
Log.info("Waiting for map renderer to stop is interrupted");
}
}
}
private void running() {
try {
while (Thread.currentThread() == thread) {
synchronized(lock) {
while(pendingcnt >= pendinglimit) {
try {
lock.wait(accelDequeueTime);
} catch (InterruptedException ix) {
if(Thread.currentThread() != thread)
return;
throw ix;
}
}
}
T t = pop();
if (t != null) {
synchronized(lock) {
pendingcnt++;
}
handler.handle(t);
}
if(set.size() >= accelDequeueThresh)
sleep(accelDequeueTime);
else
sleep(dequeueTime);
}
} catch (Exception ex) {
Log.severe("Exception on rendering-thread", ex);
}
}
private boolean sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
return false;
}
return true;
}
public void done(T t) {
synchronized (lock) {
if(pendingcnt > 0) pendingcnt--;
lock.notifyAll();
}
}
}

View file

@ -0,0 +1,12 @@
package org.dynmap;
public class ChatEvent {
public String source;
public String name;
public String message;
public ChatEvent(String source, String name, String message) {
this.source = source;
this.name = name;
this.message = message;
}
}

View file

@ -0,0 +1,339 @@
package org.dynmap;
import java.io.IOException;
import java.io.Writer;
import java.util.Random;
import org.json.simple.JSONAware;
import org.json.simple.JSONStreamAware;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
import org.dynmap.common.DynmapChatColor;
public class Client {
public static class Update implements JSONAware, JSONStreamAware {
public long timestamp = System.currentTimeMillis();
@Override
public String toJSONString() {
return org.dynmap.web.Json.stringifyJson(this);
}
@Override
public void writeJSONString(Writer w) throws IOException {
w.write(toJSONString());
}
}
public static class ChatMessage extends Update {
public String type = "chat";
public String source;
public String playerName; // Note: this needs to be client-safe HTML text (can include tags, but only sanitized ones)
public String message;
public String account;
public String channel;
public ChatMessage(String source, String channel, String playerName, String message, String playeraccount) {
this.source = source;
if (ClientUpdateComponent.hideNames)
this.playerName = "";
else if (ClientUpdateComponent.usePlayerColors)
this.playerName = Client.encodeColorInHTML(playerName);
else
this.playerName = Client.stripColor(playerName);
this.message = DynmapChatColor.stripColor(message);
this.account = playeraccount;
this.channel = channel;
}
@Override
public boolean equals(Object o) {
if(o instanceof ChatMessage) {
ChatMessage m = (ChatMessage)o;
return m.source.equals(source) && m.playerName.equals(playerName) && m.message.equals(message);
}
return false;
}
@Override
public int hashCode() {
return source.hashCode() ^ playerName.hashCode() ^ message.hashCode();
}
}
public static class PlayerJoinMessage extends Update {
public String type = "playerjoin";
public String playerName; // Note: this needs to be client-safe HTML text (can include tags, but only sanitized ones)
public String account;
public PlayerJoinMessage(String playerName, String playeraccount) {
if (ClientUpdateComponent.hideNames)
this.playerName = "";
else if (ClientUpdateComponent.usePlayerColors)
this.playerName = Client.encodeColorInHTML(playerName);
else
this.playerName = Client.stripColor(playerName);
this.account = playeraccount;
}
@Override
public boolean equals(Object o) {
if(o instanceof PlayerJoinMessage) {
PlayerJoinMessage m = (PlayerJoinMessage)o;
return m.playerName.equals(playerName);
}
return false;
}
@Override
public int hashCode() {
return account.hashCode();
}
}
public static class PlayerQuitMessage extends Update {
public String type = "playerquit";
public String playerName; // Note: this needs to be client-safe HTML text (can include tags, but only sanitized ones)
public String account;
public PlayerQuitMessage(String playerName, String playeraccount) {
if (ClientUpdateComponent.hideNames)
this.playerName = "";
else if (ClientUpdateComponent.usePlayerColors)
this.playerName = Client.encodeColorInHTML(playerName);
else
this.playerName = Client.stripColor(playerName);
this.account = playeraccount;
}
@Override
public boolean equals(Object o) {
if(o instanceof PlayerQuitMessage) {
PlayerQuitMessage m = (PlayerQuitMessage)o;
return m.playerName.equals(playerName);
}
return false;
}
@Override
public int hashCode() {
return account.hashCode();
}
}
public static class Tile extends Update {
public String type = "tile";
public String name;
public Tile(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if(o instanceof Tile) {
Tile m = (Tile)o;
return m.name.equals(name);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
}
public static class DayNight extends Update {
public String type = "daynight";
public boolean isday;
public DayNight(boolean isday) {
this.isday = isday;
}
@Override
public boolean equals(Object o) {
if(o instanceof DayNight) {
return true;
}
return false;
}
@Override
public int hashCode() {
return 12345;
}
}
public static class ComponentMessage extends Update {
public String type = "component";
/* Each subclass must provide 'ctype' string for component 'type' */
}
// Strip color - assume we're returning safe html text
public static String stripColor(String s) {
s = DynmapChatColor.stripColor(s); /* Strip standard color encoding */
/* Handle Essentials nickname encoding too */
int idx = 0;
while((idx = s.indexOf('&', idx)) >= 0) {
char c = s.charAt(idx+1); /* Get next character */
if(c == '&') { /* Another ampersand */
s = s.substring(0, idx) + s.substring(idx+1);
}
else {
s = s.substring(0, idx) + s.substring(idx+2);
}
idx++;
}
// Apply sanitize policy before returning
return sanitizeHTML(s);
}
private static String[][] codes = {
{ "0", "<span style=\'color:#000000\'>" },
{ "1", "<span style=\'color:#0000AA\'>" },
{ "2", "<span style=\'color:#00AA00\'>" },
{ "3", "<span style=\'color:#00AAAA\'>" },
{ "4", "<span style=\'color:#AA0000\'>" },
{ "5", "<span style=\'color:#AA00AA\'>" },
{ "6", "<span style=\'color:#FFAA00\'>" },
{ "7", "<span style=\'color:#AAAAAA\'>" },
{ "8", "<span style=\'color:#555555\'>" },
{ "9", "<span style=\'color:#5555FF\'>" },
{ "a", "<span style=\'color:#55FF55\'>" },
{ "b", "<span style=\'color:#55FFFF\'>" },
{ "c", "<span style=\'color:#FF5555\'>" },
{ "d", "<span style=\'color:#FF55FF\'>" },
{ "e", "<span style=\'color:#FFFF55\'>" },
{ "f", "<span style=\'color:#FFFFFF\'>" },
{ "l", "<span style=\'font-weight:bold\'>" },
{ "m", "<span style=\'text-decoration:line-through\'>" },
{ "n", "<span style=\'text-decoration:underline\'>" },
{ "o", "<span style=\'font-style:italic\'>" },
{ "r", "<span style=\'font-style:normal,text-decoration:none,font-weight:normal\'>" }
};
private static Random rnd = new Random();
private static String rndchars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Replace color codes with corresponding <span - assume we're returning safe HTML text
public static String encodeColorInHTML(String s) {
StringBuilder sb = new StringBuilder();
int cnt = s.length();
int spancnt = 0;
boolean magic = false;
for (int i = 0; i < cnt; i++) {
char c = s.charAt(i);
if (c == '\u00A7') { // Escape?
i++; // Move past it
c = s.charAt(i);
if (c == 'k') { // Magic text?
magic = true;
}
else if (c == 'r') { // reset
magic = false;
}
for (int j = 0; j < codes.length; j++) {
if (codes[j][0].charAt(0) == c) { // Matching code?
sb.append(codes[j][1]); // Substitute
spancnt++;
break;
}
else if (c == 'x') { // Essentials nickname hexcode format
if (i + 12 <= cnt){ // Check if string is at least long enough to be valid hexcode
if (s.charAt(i+1) == s.charAt(i+3) &&
s.charAt(i+1) == s.charAt(i+5) &&
s.charAt(i+1) == s.charAt(i+7) &&
s.charAt(i+1) == s.charAt(i+9) &&
s.charAt(i+1) == s.charAt(i+11) && // Check if there are enough \u00A7 in a row
s.charAt(i+1) == '\u00A7'){
StringBuilder hex = new StringBuilder().append(s.charAt(i+2))
.append(s.charAt(i+4))
.append(s.charAt(i+6))
.append(s.charAt(i+8))
.append(s.charAt(i+10))
.append(s.charAt(i+12)); // Build hexcode string
sb.append("<span style=\'color:#" + hex + "\'>"); // Substitute with hexcode
i = i + 12; //move past hex codes
}
}
break;
}
}
}
else if (c == '&') { // Essentials color code?
i++; // Move past it
c = s.charAt(i);
if (c == '&') { // Amp?
sb.append(c);
}
else {
if (c == 'k') { // Magic text?
magic = true;
}
else if (c == 'r') { // reset
magic = false;
}
for (int j = 0; j < codes.length; j++) {
if (codes[j][0].charAt(0) == c) { // Matching code?
sb.append(codes[j][1]); // Substitute
spancnt++;
break;
}
}
}
}
else if (magic) {
sb.append(rndchars.charAt(rnd.nextInt(rndchars.length())));
}
else {
sb.append(c);
}
}
for (int i = 0; i < spancnt; i++) {
sb.append("</span>");
}
return sanitizeHTML(sb.toString());
}
private static PolicyFactory sanitizer = null;
private static PolicyFactory OLDTAGS = new HtmlPolicyBuilder().allowElements("center", "basefont", "hr").toFactory();
public static String sanitizeHTML(String html) {
// Don't sanitize if null or no html markup
if ((html == null) || (html.indexOf('<') < 0)) return html;
PolicyFactory s = sanitizer;
if (s == null) {
// Generous but safe html formatting allowances
s = Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.IMAGES).and(Sanitizers.LINKS).and(Sanitizers.STYLES).and(Sanitizers.TABLES).and(OLDTAGS);
sanitizer = s;
}
return s.sanitize(html);
}
private static PolicyFactory stripper = null;
public static String stripHTML(String html) {
PolicyFactory s = stripper;
if (s == null) {
// Strip all taks
s = new HtmlPolicyBuilder().toFactory();
stripper = s;
}
return s.sanitize(html);
}
// Encode plain text string for HTML presentation
public static String encodeForHTML(String text) {
String s = text != null ? text : "";
StringBuilder str = new StringBuilder();
for (int j = 0; j < s.length(); j++) {
char c = s.charAt(j);
switch (c) {
case '"':
str.append("&quot;");
break;
case '&':
str.append("&amp;");
break;
case '<':
str.append("&lt;");
break;
case '>':
str.append("&gt;");
break;
case '\'':
str.append("&#39;");
break;
default:
str.append(c);
break;
}
}
return str.toString();
}
}

View file

@ -0,0 +1,67 @@
package org.dynmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class ClientComponent extends Component {
private boolean disabled;
public ClientComponent(final DynmapCore plugin, final ConfigurationNode configuration) {
super(plugin, configuration);
plugin.events.addListener("buildclientconfiguration", new Event.Listener<JSONObject>() {
@Override
public void triggered(JSONObject root) {
if(!disabled)
buildClientConfiguration(root);
}
});
}
protected void disableComponent() {
disabled = true;
}
protected void buildClientConfiguration(JSONObject root) {
JSONObject o = createClientConfiguration();
a(root, "components", o);
}
protected JSONObject createClientConfiguration() {
JSONObject o = convertMap(configuration);
o.remove("class");
return o;
}
protected static final JSONObject convertMap(Map<String, ?> m) {
JSONObject o = new JSONObject();
for(Map.Entry<String, ?> entry : m.entrySet()) {
s(o, entry.getKey(), convert(entry.getValue()));
}
return o;
}
@SuppressWarnings("unchecked")
protected static final JSONArray convertList(List<?> l) {
JSONArray o = new JSONArray();
for(Object entry : l) {
o.add(convert(entry));
}
return o;
}
@SuppressWarnings("unchecked")
protected static final Object convert(Object o) {
if (o instanceof Map<?, ?>) {
return convertMap((Map<String, ?>)o);
} else if (o instanceof List<?>) {
return convertList((List<?>)o);
}
return o;
}
}

View file

@ -0,0 +1,76 @@
package org.dynmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import org.dynmap.Event.Listener;
import org.json.simple.JSONObject;
public class ClientConfigurationComponent extends Component {
public ClientConfigurationComponent(final DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
core.events.<JSONObject>addListener("buildclientconfiguration", new Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
ConfigurationNode c = core.configuration;
s(t, "confighash", core.getConfigHashcode());
s(t, "updaterate", c.getFloat("updaterate", 1.0f));
s(t, "showplayerfacesinmenu", c.getBoolean("showplayerfacesinmenu", true));
s(t, "joinmessage", c.getString("joinmessage", "%playername% joined"));
s(t, "quitmessage", c.getString("quitmessage", "%playername% quit"));
s(t, "spammessage", c.getString("spammessage", "You may only chat once every %interval% seconds."));
s(t, "webprefix", unescapeString(c.getString("webprefix", "[WEB] ")));
s(t, "defaultzoom", c.getInteger("defaultzoom", 0));
s(t, "sidebaropened", c.getString("sidebaropened", "false"));
s(t, "dynmapversion", core.getDynmapPluginVersion());
s(t, "coreversion", core.getDynmapCoreVersion());
s(t, "cyrillic", c.getBoolean("cyrillic-support", false));
s(t, "showlayercontrol", c.getString("showlayercontrol", "true"));
s(t, "grayplayerswhenhidden", c.getBoolean("grayplayerswhenhidden", true));
s(t, "login-enabled", core.isLoginSupportEnabled());
String sn = core.getServer().getServerName();
if(sn.equals("Unknown Server"))
sn = "Minecraft Dynamic Map";
s(t, "title", c.getString("webpage-title", sn));
s(t, "msg-maptypes", c.getString("msg/maptypes", "Map Types"));
s(t, "msg-players", c.getString("msg/players", "Players"));
s(t, "msg-chatrequireslogin", c.getString("msg/chatrequireslogin", "Chat Requires Login"));
s(t, "msg-chatnotallowed", c.getString("msg/chatnotallowed", "You are not permitted to send chat messages"));
s(t, "msg-hiddennamejoin", c.getString("msg/hiddennamejoin", "Player joined"));
s(t, "msg-hiddennamequit", c.getString("msg/hiddennamequit", "Player quit"));
s(t, "maxcount", core.getMaxPlayers());
DynmapWorld defaultWorld = null;
String defmap = null;
a(t, "worlds", null);
for(DynmapWorld world : core.mapManager.getWorlds()) {
if (world.maps.size() == 0) continue;
if (defaultWorld == null) defaultWorld = world;
JSONObject wo = new JSONObject();
s(wo, "name", world.getName());
s(wo, "title", world.getTitle());
s(wo, "protected", world.isProtected());
DynmapLocation center = world.getCenterLocation();
s(wo, "center/x", center.x);
s(wo, "center/y", center.y);
s(wo, "center/z", center.z);
s(wo, "extrazoomout", world.getExtraZoomOutLevels());
s(wo, "sealevel", world.sealevel);
s(wo, "worldheight", world.worldheight);
a(t, "worlds", wo);
for(MapType mt : world.maps) {
mt.buildClientConfiguration(wo, world);
if(defmap == null) defmap = mt.getName();
}
}
s(t, "defaultworld", c.getString("defaultworld", defaultWorld == null ? "world" : defaultWorld.getName()));
s(t, "defaultmap", c.getString("defaultmap", defmap == null ? "surface" : defmap));
if(c.getString("followmap", null) != null)
s(t, "followmap", c.getString("followmap"));
if(c.getInteger("followzoom",-1) >= 0)
s(t, "followzoom", c.getInteger("followzoom", 0));
}
});
}
}

View file

@ -0,0 +1,181 @@
package org.dynmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.util.List;
import org.dynmap.common.DynmapPlayer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class ClientUpdateComponent extends Component {
private int hideifshadow;
private int hideifunder;
private boolean hideifsneaking;
private boolean hideifspectator;
private boolean hideifinvisiblepotion;
private boolean is_protected;
public static boolean usePlayerColors;
public static boolean hideNames;
public ClientUpdateComponent(final DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
hideNames = configuration.getBoolean("hidenames", false);
hideifshadow = configuration.getInteger("hideifshadow", 15);
hideifunder = configuration.getInteger("hideifundercover", 15);
hideifsneaking = configuration.getBoolean("hideifsneaking", false);
hideifspectator = configuration.getBoolean("hideifspectator", false);
hideifinvisiblepotion = configuration.getBoolean("hide-if-invisiblity-potion", true);
is_protected = configuration.getBoolean("protected-player-info", false);
usePlayerColors = configuration.getBoolean("use-name-colors", false);
if(is_protected)
core.player_info_protected = true;
core.events.addListener("buildclientupdate", new Event.Listener<ClientUpdateEvent>() {
@Override
public void triggered(ClientUpdateEvent e) {
buildClientUpdate(e);
}
});
}
protected void buildClientUpdate(ClientUpdateEvent e) {
DynmapWorld world = e.world;
JSONObject u = e.update;
long since = e.timestamp;
String worldName = world.getName();
boolean see_all = true;
if(is_protected && (!e.include_all_users)) {
if(e.user != null)
see_all = core.getServer().checkPlayerPermission(e.user, "playermarkers.seeall");
else
see_all = false;
}
if((e.include_all_users) && is_protected) { /* If JSON request AND protected, leave mark for script */
s(u, "protected", true);
}
s(u, "confighash", core.getConfigHashcode());
s(u, "servertime", world.getTime() % 24000);
s(u, "hasStorm", world.hasStorm());
s(u, "isThundering", world.isThundering());
s(u, "players", new JSONArray());
List<DynmapPlayer> players = core.playerList.getVisiblePlayers();
for(DynmapPlayer p : players) {
boolean hide = false;
DynmapLocation pl = p.getLocation();
DynmapWorld pw = core.getWorld(pl.world);
if(pw == null) {
hide = true;
}
JSONObject jp = new JSONObject();
s(jp, "type", "player");
if (hideNames)
s(jp, "name", "");
else if (usePlayerColors)
s(jp, "name", Client.encodeColorInHTML(p.getDisplayName()));
else
s(jp, "name", Client.stripColor(p.getDisplayName()));
s(jp, "account", p.getName());
if((!hide) && (hideifshadow < 15)) {
if(pw.getLightLevel((int)pl.x, (int)pl.y, (int)pl.z) <= hideifshadow) {
hide = true;
}
}
if((!hide) && (hideifunder < 15)) {
if(pw.canGetSkyLightLevel()) { /* If we can get real sky level */
if(pw.getSkyLightLevel((int)pl.x, (int)pl.y, (int)pl.z) <= hideifunder) {
hide = true;
}
}
else if(pw.isNether() == false) { /* Not nether */
if(pw.getHighestBlockYAt((int)pl.x, (int)pl.z) > pl.y) {
hide = true;
}
}
}
if((!hide) && hideifsneaking && p.isSneaking()) {
hide = true;
}
if((!hide) && hideifspectator && p.isSpectator()) {
hide = true;
}
if((!hide) && is_protected && (!see_all)) {
if(e.user != null) {
hide = !core.testIfPlayerVisibleToPlayer(e.user, p.getName());
}
else {
hide = true;
}
}
if((!hide) && hideifinvisiblepotion && p.isInvisible()) {
hide = true;
}
/* Don't leak player location for world not visible on maps, or if sendposition disbaled */
DynmapWorld pworld = MapManager.mapman.worldsLookup.get(pl.world);
/* Fix typo on 'sendpositon' to 'sendposition', keep bad one in case someone used it */
if(configuration.getBoolean("sendposition", true) && configuration.getBoolean("sendpositon", true) &&
(pworld != null) && pworld.sendposition && (!hide)) {
s(jp, "world", pl.world);
s(jp, "x", pl.x);
s(jp, "y", pl.y);
s(jp, "z", pl.z);
}
else {
s(jp, "world", "-some-other-bogus-world-");
s(jp, "x", 0.0);
s(jp, "y", 64.0);
s(jp, "z", 0.0);
}
/* Only send health if enabled AND we're on visible world */
if (configuration.getBoolean("sendhealth", false) && (pworld != null) && pworld.sendhealth && (!hide)) {
s(jp, "health", p.getHealth());
s(jp, "armor", p.getArmorPoints());
}
else {
s(jp, "health", 0);
s(jp, "armor", 0);
}
s(jp, "sort", p.getSortWeight());
a(u, "players", jp);
}
List<DynmapPlayer> hidden = core.playerList.getHiddenPlayers();
if(configuration.getBoolean("includehiddenplayers", false)) {
for(DynmapPlayer p : hidden) {
JSONObject jp = new JSONObject();
s(jp, "type", "player");
if (hideNames)
s(jp, "name", "");
else if (usePlayerColors)
s(jp, "name", Client.encodeColorInHTML(p.getDisplayName()));
else
s(jp, "name", Client.stripColor(p.getDisplayName()));
s(jp, "account", p.getName());
s(jp, "world", "-hidden-player-");
s(jp, "x", 0.0);
s(jp, "y", 64.0);
s(jp, "z", 0.0);
s(jp, "health", 0);
s(jp, "armor", 0);
s(jp, "sort", p.getSortWeight());
a(u, "players", jp);
}
s(u, "currentcount", core.getCurrentPlayers());
}
else {
s(u, "currentcount", core.getCurrentPlayers() - hidden.size());
}
s(u, "updates", new JSONArray());
for(Object update : core.mapManager.getWorldUpdates(worldName, since)) {
a(u, "updates", (Client.Update)update);
}
}
}

View file

@ -0,0 +1,17 @@
package org.dynmap;
import org.json.simple.JSONObject;
public class ClientUpdateEvent {
public long timestamp;
public DynmapWorld world;
public JSONObject update;
public String user;
public boolean include_all_users;
public ClientUpdateEvent(long timestamp, DynmapWorld world, JSONObject update) {
this.timestamp = timestamp;
this.world = world;
this.update = update;
}
}

View file

@ -0,0 +1,127 @@
package org.dynmap;
/**
* Simple replacement for java.awt.Color for dynmap - it's not an invariant, so we don't make millions
* of them during rendering
*/
public class Color {
/* ARGB value */
private int val;
public static final int TRANSPARENT = 0;
public Color(int red, int green, int blue, int alpha) {
setRGBA(red, green, blue, alpha);
}
public Color(int red, int green, int blue) {
setRGBA(red, green, blue, 0xFF);
}
public Color() {
setTransparent();
}
public final int getRed() {
return (val >> 16) & 0xFF;
}
public final int getGreen() {
return (val >> 8) & 0xFF;
}
public final int getBlue() {
return val & 0xFF;
}
public final int getAlpha() {
return ((val >> 24) & 0xFF);
}
public final boolean isTransparent() {
return ((val & 0xFF000000) == TRANSPARENT);
}
public final void setTransparent() {
val = TRANSPARENT;
}
public final void setGrayscale() {
int alpha = val & 0xFF000000;
int num = (((val >> 16) & 0xFF) * 76)
+ (((val >> 8) & 0xFF) * 151)
+ (( val & 0xFF) * 28);
// weights sum to 255, so num [0, 65025]; fast /255 via shift
int gray = (num + (num >> 8) + 1) >> 8;
val = alpha | (gray << 16) | (gray << 8) | gray;
}
public final void setColor(Color c) {
val = c.val;
}
public final void setRGBA(int red, int green, int blue, int alpha) {
val = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
}
public final int getARGB() {
return val;
}
public final void setARGB(int c) {
val = c;
}
public final int getComponent(int idx) {
return 0xFF & (val >> ((3-idx)*8));
}
public final void setAlpha(int v) {
val = (val & 0x00FFFFFF) | (v << 24);
}
public final void scaleColor(Color minimum, Color maximum) {
int alpha = (val >> 24) & 0xFF;
int red = (val >> 16) & 0xFF;
int green = (val >> 8) & 0xFF;
int blue = val & 0xFF;
red = minimum.getRed() + ((maximum.getRed() - minimum.getRed()) * red) / 256;
green = minimum.getGreen() + ((maximum.getGreen() - minimum.getGreen()) * green) / 256;
blue = minimum.getBlue() + ((maximum.getBlue() - minimum.getBlue()) * blue) / 256;
setRGBA(red, green, blue, alpha);
}
/**
* Scale each color component, based on the corresponding component
* @param c - color to blend
*/
public final void blendColor(Color c) {
blendColor(c.val);
}
/**
* Scale each color component, based on the corresponding component
* @param argb - ARGB to blend
*/
public final void blendColor(int argb) {
val = (mulDiv255(val >>> 24, argb >>> 24 ) << 24)
| (mulDiv255((val >> 16) & 0xFF, (argb >> 16) & 0xFF) << 16)
| (mulDiv255((val >> 8) & 0xFF, (argb >> 8) & 0xFF) << 8)
| mulDiv255( val & 0xFF, argb & 0xFF);
}
/**
* Scale each color component, based on the corresponding component
* @param argb0 - first color
* @param argb1 second color
* @return blended color
*/
public static final int blendColor(int argb0, int argb1) {
return (mulDiv255(argb0 >>> 24, argb1 >>> 24 ) << 24)
| (mulDiv255((argb0 >> 16) & 0xFF, (argb1 >> 16) & 0xFF) << 16)
| (mulDiv255((argb0 >> 8) & 0xFF, (argb1 >> 8) & 0xFF) << 8)
| mulDiv255( argb0 & 0xFF, argb1 & 0xFF);
}
/**
* Scale the RGB channels by scale/256, leaving alpha unchanged.
* Equivalent to setRGBA(getRed()*scale>>8, getGreen()*scale>>8, getBlue()*scale>>8, getAlpha())
* but avoids redundant unpack/repack of the alpha channel.
* @param scale - scale factor 0..256 (256 = full brightness)
*/
public final void scaleRGB(int scale) {
val = (val & 0xFF000000)
| ((((val >> 16) & 0xFF) * scale >> 8) << 16)
| ((((val >> 8) & 0xFF) * scale >> 8) << 8)
| ((val & 0xFF) * scale >> 8);
}
/**
* Fast multiply-then-divide-by-255 for two values a, b each in [0, 255].
* Returns floor(a*b/255), equivalent to the standard integer division but
* computed with shifts only: (x + (x >> 8) + 1) >> 8 where x = a * b.
*/
private static int mulDiv255(int a, int b) {
int x = a * b;
return (x + (x >> 8) + 1) >> 8;
}
}

View file

@ -0,0 +1,268 @@
package org.dynmap;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Scanner;
import org.dynmap.common.BiomeMap;
import org.dynmap.debug.Debug;
import org.dynmap.renderer.DynmapBlockState;
public class ColorScheme {
private static final HashMap<String, ColorScheme> cache = new HashMap<String, ColorScheme>();
public String name;
/* Switch to arrays - faster than map */
public final Color[][] colors; /* [global-state-idx][step] */
public final Color[][] biomecolors; /* [Biome.ordinal][step] */
public final Color[][] raincolors; /* [rain * 63][step] */
public final Color[][] tempcolors; /* [temp * 63][step] */
public ColorScheme(String name, Color[][] colors, Color[][] biomecolors, Color[][] raincolors,
Color[][] tempcolors) {
this.name = name;
this.colors = colors;
this.biomecolors = biomecolors;
this.raincolors = raincolors;
this.tempcolors = tempcolors;
}
private static File getColorSchemeDirectory(DynmapCore core) {
return new File(core.getDataFolder(), "colorschemes");
}
public static ColorScheme getScheme(DynmapCore core, String name) {
if (name == null)
name = "default";
ColorScheme scheme = cache.get(name);
if (scheme == null) {
scheme = loadScheme(core, name);
cache.put(name, scheme);
}
return scheme;
}
public static ColorScheme loadScheme(DynmapCore core, String name) {
File colorSchemeFile = new File(getColorSchemeDirectory(core), name + ".txt");
Color[][] colors = new Color[DynmapBlockState.getGlobalIndexMax()][];
Color[][] biomecolors = new Color[BiomeMap.values().length][];
Color[][] raincolors = new Color[64][];
Color[][] tempcolors = new Color[64][];
/* Default the biome color */
for (int i = 0; i < biomecolors.length; i++) {
Color[] c = new Color[5];
int red = 0x80 | (0x40 * ((i >> 0) & 1)) | (0x20 * ((i >> 3) & 1)) | (0x10 * ((i >> 6) & 1));
int green = 0x80 | (0x40 * ((i >> 1) & 1)) | (0x20 * ((i >> 4) & 1)) | (0x10 * ((i >> 7) & 1));
int blue = 0x80 | (0x40 * ((i >> 2) & 1)) | (0x20 * ((i >> 5) & 1));
c[0] = new Color(red, green, blue);
c[3] = new Color(red * 4 / 5, green * 4 / 5, blue * 4 / 5);
c[1] = new Color(red / 2, green / 2, blue / 2);
c[2] = new Color(red * 2 / 5, green * 2 / 5, blue * 2 / 5);
c[4] = new Color((c[1].getRed() + c[3].getRed()) / 2, (c[1].getGreen() + c[3].getGreen()) / 2,
(c[1].getBlue() + c[3].getBlue()) / 2, (c[1].getAlpha() + c[3].getAlpha()) / 2);
biomecolors[i] = c;
}
InputStream stream;
// Get defaults from biome_rainfall_temp.txt - let custom file override after
File files[] = {
new File(getColorSchemeDirectory(core), "biome_rainfall_temp.txt"),
new File(getColorSchemeDirectory(core), "default.txt"),
colorSchemeFile };
try {
for (int fidx = 0; fidx < files.length; fidx++) {
Debug.debug("Loading colors from '" + files[fidx] + "' for " + name + "...");
stream = new FileInputStream(files[fidx]);
Scanner scanner = new Scanner(stream);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.startsWith("#") || line.equals("")) {
continue;
}
/* Make parser less pedantic - tabs or spaces should be fine */
String[] split = line.split("[\t ]");
int cnt = 0;
for (String s : split) {
if (s.length() > 0)
cnt++;
}
String[] nsplit = new String[cnt];
cnt = 0;
for (String s : split) {
if (s.length() > 0) {
nsplit[cnt] = s;
cnt++;
}
}
split = nsplit;
if (split.length < 17) {
continue;
}
Integer id = null;
boolean isbiome = false;
boolean istemp = false;
boolean israin = false;
DynmapBlockState state = null;
int idx = split[0].indexOf(':');
if (idx > 0) { /* ID:data - data color OR blockstate - data color */
String[] vsplit = split[0].split("[\\[\\]]");
// Log.info(String.format("split[0]=%s,vsplit[0]=%s,vsplit[1]=%s", split[0],
// vsplit[0], vsplit.length > 1 ? vsplit[1] : ""));
if (vsplit.length > 1) {
state = DynmapBlockState.getStateByNameAndState(vsplit[0], vsplit[1]);
} else {
state = DynmapBlockState.getBaseStateByName(vsplit[0]);
}
} else if (split[0].charAt(0) == '[') { /* Biome color data */
String bio = split[0].substring(1);
idx = bio.indexOf(']');
if (idx >= 0)
bio = bio.substring(0, idx);
isbiome = true;
id = -1;
BiomeMap[] bm = BiomeMap.values();
for (int i = 0; i < bm.length; i++) {
if (bm[i].getId().equalsIgnoreCase(bio)) {
id = i;
break;
} else if (bio.equalsIgnoreCase("BIOME_" + i)) {
id = i;
break;
}
}
if (id < 0) { /* Not biome - check for rain or temp */
if (bio.startsWith("RAINFALL-")) {
try {
double v = Double.parseDouble(bio.substring(9));
if ((v >= 0) && (v <= 1.00)) {
id = (int) (v * 63.0);
israin = true;
}
} catch (NumberFormatException nfx) {
}
} else if (bio.startsWith("TEMPERATURE-")) {
try {
double v = Double.parseDouble(bio.substring(12));
if ((v >= 0) && (v <= 1.00)) {
id = (int) (v * 63.0);
istemp = true;
}
} catch (NumberFormatException nfx) {
}
}
}
} else {
id = Integer.parseInt(split[0]);
state = DynmapBlockState.getStateByLegacyBlockID(id);
}
Color[] c = new Color[5];
/* store colors by raycast sequence number */
c[0] = new Color(Integer.parseInt(split[1]), Integer.parseInt(split[2]), Integer.parseInt(split[3]),
Integer.parseInt(split[4]));
c[3] = new Color(Integer.parseInt(split[5]), Integer.parseInt(split[6]), Integer.parseInt(split[7]),
Integer.parseInt(split[8]));
c[1] = new Color(Integer.parseInt(split[9]), Integer.parseInt(split[10]),
Integer.parseInt(split[11]), Integer.parseInt(split[12]));
c[2] = new Color(Integer.parseInt(split[13]), Integer.parseInt(split[14]),
Integer.parseInt(split[15]), Integer.parseInt(split[16]));
/* Blended color - for 'smooth' option on flat map */
c[4] = new Color((c[1].getRed() + c[3].getRed()) / 2, (c[1].getGreen() + c[3].getGreen()) / 2,
(c[1].getBlue() + c[3].getBlue()) / 2, (c[1].getAlpha() + c[3].getAlpha()) / 2);
if (isbiome) {
if (istemp) {
tempcolors[id] = c;
} else if (israin) {
raincolors[id] = c;
} else if ((id >= 0) && (id < biomecolors.length))
biomecolors[id] = c;
} else if (state != null) {
int stateid = state.globalStateIndex;
colors[stateid] = c;
}
}
scanner.close();
}
/* Last, push base color into any open slots in data colors list */
for (int i = 0; i < colors.length; i++) {
if (colors[i] == null) {
DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(i); // Get state
DynmapBlockState bsbase = bs.baseState;
if ((bsbase != null) && (colors[bsbase.globalStateIndex] != null)) {
colors[i] = colors[bsbase.globalStateIndex];
}
}
}
/* And interpolate any missing rain and temperature colors */
interpolateColorTable(tempcolors);
interpolateColorTable(raincolors);
} catch (RuntimeException e) {
Log.severe("Could not load colors '" + name + "' ('" + colorSchemeFile + "').", e);
return null;
} catch (FileNotFoundException e) {
Log.severe("Could not load colors '" + name + "' ('" + colorSchemeFile + "'): File not found.", e);
}
return new ColorScheme(name, colors, biomecolors, raincolors, tempcolors);
}
public static void interpolateColorTable(Color[][] c) {
int idx = -1;
for (int k = 0; k < c.length; k++) {
if (c[k] == null) { /* Missing? */
if ((idx >= 0) && (k == (c.length - 1))) { /* We're last - so fill forward from last color */
for (int kk = idx + 1; kk <= k; kk++) {
c[kk] = c[idx];
}
}
/* Skip - will backfill when we find next color */
} else if (idx == -1) { /* No previous color, just backfill this color */
for (int kk = 0; kk < k; kk++) {
c[kk] = c[k];
}
idx = k; /* This is now last defined color */
} else { /* Else, interpolate between last idx and this one */
int cnt = c[k].length;
for (int kk = idx + 1; kk < k; kk++) {
double interp = (double) (kk - idx) / (double) (k - idx);
Color[] cc = new Color[cnt];
for (int jj = 0; jj < cnt; jj++) {
cc[jj] = new Color((int) ((1.0 - interp) * c[idx][jj].getRed() + interp * c[k][jj].getRed()),
(int) ((1.0 - interp) * c[idx][jj].getGreen() + interp * c[k][jj].getGreen()),
(int) ((1.0 - interp) * c[idx][jj].getBlue() + interp * c[k][jj].getBlue()),
(int) ((1.0 - interp) * c[idx][jj].getAlpha() + interp * c[k][jj].getAlpha()));
}
c[kk] = cc;
}
idx = k;
}
}
}
public Color[] getRainColor(double rain) {
int idx = (int) (rain * 63.0);
if ((idx >= 0) && (idx < raincolors.length))
return raincolors[idx];
else
return null;
}
public Color[] getTempColor(double temp) {
int idx = (int) (temp * 63.0);
if ((idx >= 0) && (idx < tempcolors.length))
return tempcolors[idx];
else
return null;
}
public static void reset() {
cache.clear();
}
}

View file

@ -0,0 +1,21 @@
package org.dynmap;
public abstract class Component {
protected DynmapCore core;
protected ConfigurationNode configuration;
public Component(DynmapCore core, ConfigurationNode configuration) {
this.core = core;
this.configuration = configuration;
}
public void dispose() {
}
/* Substitute proper values for escape sequences */
public static String unescapeString(String v) {
/* Replace color code &color; */
v = v.replace("&color;", "\u00A7");
return v;
}
}

View file

@ -0,0 +1,52 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.StreamSupport;
public class ComponentManager {
public Set<Component> components = new HashSet<Component>();
public Map<String, List<Component>> componentLookup = new HashMap<String, List<Component>>();
public void add(Component c) {
if (components.add(c)) {
String key = c.getClass().toString();
List<Component> clist = componentLookup.get(key);
if (clist == null) {
clist = new ArrayList<Component>();
componentLookup.put(key, clist);
}
clist.add(c);
}
}
public void remove(Component c) {
if (components.remove(c)) {
String key = c.getClass().toString();
List<Component> clist = componentLookup.get(key);
if (clist != null) {
clist.remove(c);
}
}
}
public void clear() {
componentLookup.clear();
components.clear();
}
public Iterable<Component> getComponents(Class<? extends Component> c) {
List<Component> list = componentLookup.get(c.toString());
if (list == null)
return new ArrayList<Component>();
return list;
}
public Boolean isLoaded(Class<? extends Component> c){
return StreamSupport.stream(getComponents(c).spliterator(), false).count() > 0;
}
}

View file

@ -0,0 +1,468 @@
package org.dynmap;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.nodes.CollectionNode;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.reader.UnicodeReader;
import org.yaml.snakeyaml.representer.Represent;
import org.yaml.snakeyaml.representer.Representer;
public class ConfigurationNode implements Map<String, Object> {
public Map<String, Object> entries;
private File f;
private Yaml yaml;
public ConfigurationNode() {
entries = new LinkedHashMap<String, Object>();
}
private void initparse() {
if(yaml == null) {
DumperOptions options = new DumperOptions();
options.setIndent(4);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
options.setVersion(DumperOptions.Version.V1_1);
yaml = new Yaml(new SafeConstructor(), new EmptyNullRepresenter(), options);
}
}
public ConfigurationNode(File f) {
this.f = f;
entries = new LinkedHashMap<String, Object>();
}
public ConfigurationNode(Map<String, Object> map) {
if (map == null) {
throw new IllegalArgumentException();
}
entries = map;
}
public ConfigurationNode(InputStream in) {
load(in);
}
@SuppressWarnings("unchecked")
public boolean load(InputStream in) {
initparse();
Object o = yaml.load(new UnicodeReader(in));
if((o != null) && (o instanceof Map))
entries = (Map<String, Object>)o;
return (entries != null);
}
@SuppressWarnings("unchecked")
public boolean load() {
initparse();
// If no file to read, just return false
if (!f.canRead()) { return false; }
Reader fr = null;
try {
fr = new UnicodeReader(new BufferedInputStream(new FileInputStream(f)));
Object o = yaml.load(fr);
if((o != null) && (o instanceof Map))
entries = (Map<String, Object>)o;
fr.close();
}
catch (YAMLException e) {
Log.severe("Error parsing " + f.getPath() + ". Use http://yamllint.com to debug the YAML syntax." );
throw e;
} catch(IOException iox) {
Log.severe("Error reading " + f.getPath());
return false;
} finally {
if(fr != null) {
try { fr.close(); } catch (IOException x) {}
}
}
return (entries != null);
}
public boolean save() {
return save(f);
}
public boolean save(File file) {
initparse();
OutputStream stream = null;
File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
try {
stream = new BufferedOutputStream(new FileOutputStream(file));
OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8");
yaml.dump(entries, writer);
return true;
} catch (IOException e) {
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {}
}
return false;
}
@SuppressWarnings("unchecked")
public Object getObject(String path) {
if (path.isEmpty())
return entries;
// Try get first (in case '/' is legit part
Object v = get(path);
int separator = path.indexOf('/');
if ((v != null) || (separator < 0)) return v;
String localKey = path.substring(0, separator);
Object subvalue = get(localKey);
if (subvalue == null)
return null;
if (!(subvalue instanceof Map<?, ?>))
return null;
Map<String, Object> submap;
try {
submap = (Map<String, Object>)subvalue;
} catch (ClassCastException e) {
return null;
}
String subpath = path.substring(separator + 1);
return new ConfigurationNode(submap).getObject(subpath);
}
public Object getObject(String path, Object def) {
Object o = getObject(path);
if (o == null)
return def;
return o;
}
@SuppressWarnings("unchecked")
public <T> T getGeneric(String path, T def) {
Object o = getObject(path, def);
try {
return (T)o;
} catch(ClassCastException e) {
return def;
}
}
public int getInteger(String path, int def) {
return Integer.parseInt(getObject(path, def).toString());
}
public double getLong(String path, long def) {
return Long.parseLong(getObject(path, def).toString());
}
public float getFloat(String path, float def) {
return Float.parseFloat(getObject(path, def).toString());
}
public double getDouble(String path, double def) {
return Double.parseDouble(getObject(path, def).toString());
}
public boolean getBoolean(String path, boolean def) {
return Boolean.parseBoolean(getObject(path, def).toString());
}
public String getString(String path) {
return getString(path, null);
}
public List<String> getStrings(String path, List<String> def) {
Object o = getObject(path);
if (!(o instanceof List<?>)) {
return def;
}
ArrayList<String> strings = new ArrayList<String>();
for(Object i : (List<?>)o) {
strings.add(i.toString());
}
return strings;
}
public String getString(String path, String def) {
Object o = getObject(path, def);
if (o == null)
return null;
return o.toString();
}
public Color getColor(String path, String def) {
String lclr = this.getString(path, def);
if((lclr != null) && (lclr.startsWith("#"))) {
try {
int c = Integer.parseInt(lclr.substring(1), 16);
return new Color((c>>16)&0xFF, (c>>8)&0xFF, c&0xFF);
} catch (NumberFormatException nfx) {
Log.severe("Invalid color value: " + lclr + " for '" + path + "'");
}
}
return null;
}
@SuppressWarnings("unchecked")
public <T> List<T> getList(String path) {
try {
List<T> list = (List<T>)getObject(path, null);
return list;
} catch (ClassCastException e) {
try {
T o = (T)getObject(path, null);
if (o == null) {
return new ArrayList<T>();
}
ArrayList<T> al = new ArrayList<T>();
al.add(o);
return al;
} catch (ClassCastException e2) {
return new ArrayList<T>();
}
}
}
public List<Map<String,Object>> getMapList(String path) {
return getList(path);
}
public ConfigurationNode getNode(String path) {
Map<String, Object> v = null;
v = getGeneric(path, v);
if (v == null)
return null;
return new ConfigurationNode(v);
}
@SuppressWarnings("unchecked")
public List<ConfigurationNode> getNodes(String path) {
List<Object> o = getList(path);
if(o == null)
return new ArrayList<ConfigurationNode>();
ArrayList<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
for(Object i : (List<?>)o) {
if (i instanceof Map<?, ?>) {
Map<String, Object> map;
try {
map = (Map<String, Object>)i;
} catch(ClassCastException e) {
continue;
}
nodes.add(new ConfigurationNode(map));
}
}
return nodes;
}
public void extend(Map<String, Object> other) {
if (other != null)
extendMap(this, other);
}
private final static Object copyValue(Object v) {
if(v instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> mv = (Map<String, Object>)v;
LinkedHashMap<String, Object> newv = new LinkedHashMap<String,Object>();
for(Map.Entry<String, Object> me : mv.entrySet()) {
newv.put(me.getKey(), copyValue(me.getValue()));
}
return newv;
}
else if(v instanceof List) {
@SuppressWarnings("unchecked")
List<Object> lv = (List<Object>)v;
ArrayList<Object> newv = new ArrayList<Object>();
for(int i = 0; i < lv.size(); i++) {
newv.add(copyValue(lv.get(i)));
}
return newv;
}
else {
return v;
}
}
private final static void extendMap(Map<String, Object> left, Map<String, Object> right) {
ConfigurationNode original = new ConfigurationNode(left);
for(Map.Entry<String, Object> entry : right.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
original.put(key, copyValue(value));
}
}
public <T> T createInstance(Class<?>[] constructorParameters, Object[] constructorArguments) {
String typeName = getString("class");
try {
Class<?> mapTypeClass = Class.forName(typeName);
Class<?>[] constructorParameterWithConfiguration = new Class<?>[constructorParameters.length+1];
for(int i = 0; i < constructorParameters.length; i++) { constructorParameterWithConfiguration[i] = constructorParameters[i]; }
constructorParameterWithConfiguration[constructorParameterWithConfiguration.length-1] = ConfigurationNode.class;
Object[] constructorArgumentsWithConfiguration = new Object[constructorArguments.length+1];
for(int i = 0; i < constructorArguments.length; i++) { constructorArgumentsWithConfiguration[i] = constructorArguments[i]; }
constructorArgumentsWithConfiguration[constructorArgumentsWithConfiguration.length-1] = this;
Constructor<?> constructor = mapTypeClass.getConstructor(constructorParameterWithConfiguration);
@SuppressWarnings("unchecked")
T t = (T)constructor.newInstance(constructorArgumentsWithConfiguration);
return t;
} catch (Exception e) {
// TODO: Remove reference to MapManager.
Log.severe("Error loading maptype", e);
e.printStackTrace();
}
return null;
}
public <T> List<T> createInstances(String path, Class<?>[] constructorParameters, Object[] constructorArguments) {
List<ConfigurationNode> nodes = getNodes(path);
List<T> instances = new ArrayList<T>();
for(ConfigurationNode node : nodes) {
T instance = node.<T>createInstance(constructorParameters, constructorArguments);
if (instance != null) {
instances.add(instance);
}
}
return instances;
}
@Override
public int size() {
return entries.size();
}
@Override
public boolean isEmpty() {
return entries.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return entries.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return entries.containsValue(value);
}
@Override
public Object get(Object key) {
return entries.get(key);
}
@Override
public Object put(String key, Object value) {
return entries.put(key, value);
}
@Override
public Object remove(Object key) {
return entries.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends Object> m) {
entries.putAll(m);
}
@Override
public void clear() {
entries.clear();
}
@Override
public Set<String> keySet() {
return entries.keySet();
}
@Override
public Collection<Object> values() {
return entries.values();
}
@Override
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return entries.entrySet();
}
private class EmptyNullRepresenter extends Representer {
public EmptyNullRepresenter() {
super();
this.nullRepresenter = new EmptyRepresentNull();
}
protected class EmptyRepresentNull implements Represent {
public Node representData(Object data) {
return representScalar(Tag.NULL, ""); // Changed "null" to "" so as to avoid writing nulls
}
}
// Code borrowed from snakeyaml (http://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue60/SkipBeanTest.java)
@Override
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
Node valueNode = tuple.getValueNode();
if (valueNode instanceof CollectionNode) {
// Removed null check
if (Tag.SEQ.equals(valueNode.getTag())) {
SequenceNode seq = (SequenceNode) valueNode;
if (seq.getValue().isEmpty()) {
return null; // skip empty lists
}
}
if (Tag.MAP.equals(valueNode.getTag())) {
MappingNode seq = (MappingNode) valueNode;
if (seq.getValue().isEmpty()) {
return null; // skip empty maps
}
}
}
return tuple;
}
// End of borrowed code
}
}

View file

@ -0,0 +1,22 @@
package org.dynmap;
public class DynmapChunk {
public int x, z;
public DynmapChunk(int x, int z) {
this.x = x;
this.z = z;
}
@Override
public boolean equals(Object o) {
if(o instanceof DynmapChunk) {
DynmapChunk dc = (DynmapChunk)o;
return (dc.x == this.x) && (dc.z == this.z);
}
return false;
}
@Override
public int hashCode() {
return x ^ (z << 5);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
package org.dynmap;
/**
* Generic block location
*/
public class DynmapLocation {
public double x, y, z;
public String world;
public DynmapLocation() {}
public DynmapLocation(String w, double x, double y, double z) {
world = w;
this.x = x; this.y = y; this.z = z;
}
public String toString() {
return String.format("{%s,%f,%f,%f}", world, x, y, z);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,571 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.dynmap.MapType.ImageEncoding;
import org.dynmap.hdmap.TexturePack;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.utils.DynmapBufferedImage;
import org.dynmap.utils.ImageIOManager;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.RectangleVisibilityLimit;
import org.dynmap.utils.RoundVisibilityLimit;
import org.dynmap.utils.TileFlags;
import org.dynmap.utils.VisibilityLimit;
import org.dynmap.utils.Polygon;
import java.awt.image.BufferedImage;
import java.io.IOException;
public abstract class DynmapWorld {
public List<MapType> maps = new ArrayList<MapType>();
public List<MapTypeState> mapstate = new ArrayList<MapTypeState>();
public UpdateQueue updates = new UpdateQueue();
public DynmapLocation center;
public List<DynmapLocation> seedloc; /* All seed location - both direct and based on visibility limits */
private List<DynmapLocation> seedloccfg; /* Configured full render seeds only */
public List<VisibilityLimit> visibility_limits;
public List<VisibilityLimit> hidden_limits;
public MapChunkCache.HiddenChunkStyle hiddenchunkstyle;
public int servertime;
public boolean sendposition;
public boolean sendhealth;
public boolean showborder;
private int extrazoomoutlevels; /* Number of additional zoom out levels to generate */
private boolean cancelled;
private final String wname;
private final int hashcode;
private final String raw_wname;
private String title;
public int tileupdatedelay;
private boolean is_enabled;
boolean is_protected; /* If true, user needs 'dynmap.world.<worldid>' privilege to see world */
protected int[] brightnessTable = new int[16]; // 0-256 scaled brightness table
private MapStorage storage; // Storage handler for this world's maps
/* World height data */
public int worldheight; // really maxY+1
public int minY;
public int sealevel;
protected void updateWorldHeights(int worldheight, int minY, int sealevel) {
this.worldheight = worldheight;
this.minY = minY;
this.sealevel = sealevel;
}
protected DynmapWorld(String wname, int worldheight, int sealevel) {
this(wname, worldheight, sealevel, 0);
}
protected DynmapWorld(String wname, int worldheight, int sealevel, int miny) {
this.raw_wname = wname;
this.wname = normalizeWorldName(wname);
this.hashcode = this.wname.hashCode();
this.title = wname;
this.worldheight = worldheight;
this.minY = miny;
this.sealevel = sealevel;
/* Generate default brightness table for surface world */
for (int i = 0; i <= 15; ++i) {
float f1 = 1.0F - (float)i / 15.0F;
setBrightnessTableEntry(i, ((1.0F - f1) / (f1 * 3.0F + 1.0F)));
}
}
protected void setBrightnessTableEntry(int level, float value) {
if ((level < 0) || (level > 15)) return;
this.brightnessTable[level] = (int)(256.0 * value);
if (this.brightnessTable[level] > 256) this.brightnessTable[level] = 256;
if (this.brightnessTable[level] < 0) this.brightnessTable[level] = 0;
}
/**
* Get world's brightness table
* @return table
*/
public int[] getBrightnessTable() {
return brightnessTable;
}
public void setExtraZoomOutLevels(int lvl) {
extrazoomoutlevels = lvl;
}
public int getExtraZoomOutLevels() { return extrazoomoutlevels; }
public void enqueueZoomOutUpdate(MapStorageTile tile) {
MapTypeState mts = getMapState(tile.map);
if (mts != null) {
mts.setZoomOutInv(tile.x, tile.y, tile.zoom);
}
}
public void freshenZoomOutFiles() {
MapTypeState.ZoomOutCoord c = new MapTypeState.ZoomOutCoord();
for (MapTypeState mts : mapstate) {
if (cancelled) return;
MapType mt = mts.type;
MapType.ImageVariant var[] = mt.getVariants();
mts.startZoomOutIter(); // Start iterator
while (mts.nextZoomOutInv(c)) {
if(cancelled) return;
for (int varIdx = 0; varIdx < var.length; varIdx++) {
MapStorageTile tile = storage.getTile(this, mt, c.x, c.y, c.zoomlevel, var[varIdx]);
processZoomFile(mts, tile, varIdx == 0);
}
}
}
}
public void cancelZoomOutFreshen() {
cancelled = true;
}
public void activateZoomOutFreshen() {
cancelled = false;
}
private static final int[] stepseq = { 3, 1, 2, 0 };
private void processZoomFile(MapTypeState mts, MapStorageTile tile, boolean firstVariant) {
long mostRecentTimestamp = 0;
int step = 1 << tile.zoom;
MapStorageTile ztile = tile.getZoomOutTile();
int width = mts.tileSize, height = mts.tileSize;
BufferedImage zIm = null;
DynmapBufferedImage kzIm = null;
boolean blank = true;
int[] argb = new int[width*height];
int tx = ztile.x;
int ty = ztile.y;
ty = ty - step; /* Adjust for negative step */
/* create image buffer */
kzIm = DynmapBufferedImage.allocateBufferedImage(width, height);
zIm = kzIm.buf_img;
for(int i = 0; i < 4; i++) {
boolean doblit = true;
int tx1 = tx + step * (1 & stepseq[i]);
int ty1 = ty + step * (stepseq[i] >> 1);
MapStorageTile tile1 = storage.getTile(this, tile.map, tx1, ty1, tile.zoom, tile.var);
if (tile1 == null) continue;
tile1.getReadLock();
if (firstVariant) { // We're handling this one - but only clear on first variant (so that we don't miss updates later)
mts.clearZoomOutInv(tile1.x, tile1.y, tile1.zoom);
}
try {
MapStorageTile.TileRead tr = tile1.read();
if (tr != null) {
BufferedImage im = null;
try {
im = ImageIOManager.imageIODecode(tr);
// Only consider the timestamp when the tile exists and isn't broken
mostRecentTimestamp = Math.max(mostRecentTimestamp, tr.lastModified);
} catch (IOException iox) {
// Broken file - zap it
tile1.delete();
}
if((im != null) && (im.getWidth() >= width) && (im.getHeight() >= height)) {
int iwidth = im.getWidth();
int iheight = im.getHeight();
if(iwidth > iheight) iwidth = iheight;
if ((iwidth == width) && (iheight == height)) {
im.getRGB(0, 0, width, height, argb, 0, width); /* Read data */
im.flush();
/* Do binlinear scale to width/2 x height/2 */
int off = 0;
for(int y = 0; y < height; y += 2) {
off = y*width;
for(int x = 0; x < width; x += 2, off += 2) {
int p0 = argb[off];
int p1 = argb[off+1];
int p2 = argb[off+width];
int p3 = argb[off+width+1];
int alpha = ((p0 >> 24) & 0xFF) + ((p1 >> 24) & 0xFF) + ((p2 >> 24) & 0xFF) + ((p3 >> 24) & 0xFF);
int red = ((p0 >> 16) & 0xFF) + ((p1 >> 16) & 0xFF) + ((p2 >> 16) & 0xFF) + ((p3 >> 16) & 0xFF);
int green = ((p0 >> 8) & 0xFF) + ((p1 >> 8) & 0xFF) + ((p2 >> 8) & 0xFF) + ((p3 >> 8) & 0xFF);
int blue = (p0 & 0xFF) + (p1 & 0xFF) + (p2 & 0xFF) + (p3 & 0xFF);
argb[off>>1] = (((alpha>>2)&0xFF)<<24) | (((red>>2)&0xFF)<<16) | (((green>>2)&0xFF)<<8) | ((blue>>2)&0xFF);
}
}
}
else {
int[] buf = new int[iwidth * iwidth];
im.getRGB(0, 0, iwidth, iwidth, buf, 0, iwidth);
im.flush();
TexturePack.scaleTerrainPNGSubImage(iwidth, width/2, buf, argb);
/* blit scaled rendered tile onto zoom-out tile */
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width/2);
doblit = false;
}
blank = false;
}
else {
if (tile1.map.getImageFormat().getEncoding() == ImageEncoding.JPG) {
Arrays.fill(argb, tile1.map.getBackgroundARGB(tile1.var));
}
else {
Arrays.fill(argb, 0);
}
tile1.delete(); // Delete unusable tile
}
}
else {
if (tile1.map.getImageFormat().getEncoding() == ImageEncoding.JPG) {
Arrays.fill(argb, tile1.map.getBackgroundARGB(tile1.var));
}
else {
Arrays.fill(argb, 0);
}
}
} finally {
tile1.releaseReadLock();
}
/* blit scaled rendered tile onto zoom-out tile */
if(doblit) {
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width);
}
}
ztile.getWriteLock();
try {
MapManager mm = MapManager.mapman;
if(mm == null)
return;
long crc = MapStorage.calculateImageHashCode(kzIm.argb_buf, 0, kzIm.argb_buf.length); /* Get hash of tile */
if(blank) {
if (ztile.exists()) {
ztile.delete();
MapManager.mapman.pushUpdate(this, new Client.Tile(ztile.getURI()));
enqueueZoomOutUpdate(ztile);
}
}
else /* if (!ztile.matchesHashCode(crc)) */ {
ztile.write(crc, zIm, (mostRecentTimestamp == 0)? System.currentTimeMillis() : mostRecentTimestamp);
MapManager.mapman.pushUpdate(this, new Client.Tile(ztile.getURI()));
enqueueZoomOutUpdate(ztile);
}
} finally {
ztile.releaseWriteLock();
DynmapBufferedImage.freeBufferedImage(kzIm);
}
}
/* Get world name */
public String getName() {
return wname;
}
/* Test if world is nether */
public abstract boolean isNether();
/* Get world spawn location */
public abstract DynmapLocation getSpawnLocation();
public int hashCode() {
return this.hashcode;
}
/* Get world time */
public abstract long getTime();
/* World is storming */
public abstract boolean hasStorm();
/* World is thundering */
public abstract boolean isThundering();
/* World is loaded */
public abstract boolean isLoaded();
/* Set world unloaded */
public abstract void setWorldUnloaded();
/* Get light level of block */
public abstract int getLightLevel(int x, int y, int z);
/* Get highest Y coord of given location */
public abstract int getHighestBlockYAt(int x, int z);
/* Test if sky light level is requestable */
public abstract boolean canGetSkyLightLevel();
/* Return sky light level */
public abstract int getSkyLightLevel(int x, int y, int z);
/**
* Get world environment ID (lower case - normal, the_end, nether)
* @return environment ID
*/
public abstract String getEnvironment();
/**
* Get map chunk cache for world
* @param chunks - list of chunks to load
* @return cache
*/
public abstract MapChunkCache getChunkCache(List<DynmapChunk> chunks);
/**
* Get title for world
* @return title
*/
public String getTitle() {
return title;
}
/**
* Get center location
* @return center
*/
public DynmapLocation getCenterLocation() {
if(center != null)
return center;
else
return getSpawnLocation();
}
/* Load world configuration from configuration node */
public boolean loadConfiguration(DynmapCore core, ConfigurationNode worldconfig) {
is_enabled = worldconfig.getBoolean("enabled", false);
if (!is_enabled) {
return false;
}
title = worldconfig.getString("title", title);
ConfigurationNode ctr = worldconfig.getNode("center");
int mid_y = (worldheight + minY)/2;
if(ctr != null)
center = new DynmapLocation(wname, ctr.getDouble("x", 0.0), ctr.getDouble("y", mid_y), ctr.getDouble("z", 0));
else
center = null;
List<ConfigurationNode> loclist = worldconfig.getNodes("fullrenderlocations");
seedloc = new ArrayList<DynmapLocation>();
seedloccfg = new ArrayList<DynmapLocation>();
servertime = (int)(getTime() % 24000);
sendposition = worldconfig.getBoolean("sendposition", true);
sendhealth = worldconfig.getBoolean("sendhealth", true);
showborder = worldconfig.getBoolean("showborder", true);
is_protected = worldconfig.getBoolean("protected", false);
setExtraZoomOutLevels(worldconfig.getInteger("extrazoomout", 0));
setTileUpdateDelay(worldconfig.getInteger("tileupdatedelay", -1));
storage = core.getDefaultMapStorage();
if(loclist != null) {
for(ConfigurationNode loc : loclist) {
DynmapLocation lx = new DynmapLocation(wname, loc.getDouble("x", 0), loc.getDouble("y", mid_y), loc.getDouble("z", 0));
seedloc.add(lx); /* Add to both combined and configured seed list */
seedloccfg.add(lx);
}
}
/* Build maps */
maps.clear();
Log.verboseinfo("Loading maps of world '" + wname + "'...");
for(MapType map : worldconfig.<MapType>createInstances("maps", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(map.getName() != null) {
maps.add(map);
}
}
/* Rebuild map state list - match on indexes */
mapstate.clear();
for(MapType map : maps) {
MapTypeState ms = new MapTypeState(this, map);
ms.setInvalidatePeriod(map.getTileUpdateDelay(this));
mapstate.add(ms);
}
Log.info("Loaded " + maps.size() + " maps of world '" + wname + "'.");
/* Load visibility limits, if any are defined */
List<ConfigurationNode> vislimits = worldconfig.getNodes("visibilitylimits");
if(vislimits != null) {
visibility_limits = new ArrayList<VisibilityLimit>();
for(ConfigurationNode vis : vislimits) {
VisibilityLimit lim;
if (vis.containsKey("r")) { /* It is round visibility limit */
int x_center = vis.getInteger("x", 0);
int z_center = vis.getInteger("z", 0);
int radius = vis.getInteger("r", 0);
lim = new RoundVisibilityLimit(x_center, z_center, radius);
}
else { /* Rectangle visibility limit */
int x0 = vis.getInteger("x0", 0);
int x1 = vis.getInteger("x1", 0);
int z0 = vis.getInteger("z0", 0);
int z1 = vis.getInteger("z1", 0);
lim = new RectangleVisibilityLimit(x0, z0, x1, z1);
}
visibility_limits.add(lim);
/* Also, add a seed location for the middle of each visible area */
seedloc.add(new DynmapLocation(wname, lim.xCenter(), 64, lim.zCenter()));
}
}
/* Load hidden limits, if any are defined */
List<ConfigurationNode> hidelimits = worldconfig.getNodes("hiddenlimits");
if(hidelimits != null) {
hidden_limits = new ArrayList<VisibilityLimit>();
for(ConfigurationNode vis : hidelimits) {
VisibilityLimit lim;
if (vis.containsKey("r")) { /* It is round visibility limit */
int x_center = vis.getInteger("x", 0);
int z_center = vis.getInteger("z", 0);
int radius = vis.getInteger("r", 0);
lim = new RoundVisibilityLimit(x_center, z_center, radius);
}
else { /* Rectangle visibility limit */
int x0 = vis.getInteger("x0", 0);
int x1 = vis.getInteger("x1", 0);
int z0 = vis.getInteger("z0", 0);
int z1 = vis.getInteger("z1", 0);
lim = new RectangleVisibilityLimit(x0, z0, x1, z1);
}
hidden_limits.add(lim);
}
}
String hiddenchunkstyle = worldconfig.getString("hidestyle", "stone");
this.hiddenchunkstyle = MapChunkCache.HiddenChunkStyle.fromValue(hiddenchunkstyle);
if (this.hiddenchunkstyle == null) this.hiddenchunkstyle = MapChunkCache.HiddenChunkStyle.FILL_STONE_PLAIN;
return true;
}
/*
* Make configuration node for saving world
*/
public ConfigurationNode saveConfiguration() {
ConfigurationNode node = new ConfigurationNode();
/* Add name and title */
node.put("name", wname);
node.put("title", getTitle());
node.put("enabled", is_enabled);
node.put("protected", is_protected);
node.put("showborder", showborder);
if(tileupdatedelay > 0) {
node.put("tileupdatedelay", tileupdatedelay);
}
/* Add center */
if(center != null) {
ConfigurationNode c = new ConfigurationNode();
c.put("x", center.x);
c.put("y", center.y);
c.put("z", center.z);
node.put("center", c.entries);
}
/* Add seed locations, if any */
if(seedloccfg.size() > 0) {
ArrayList<Map<String,Object>> locs = new ArrayList<Map<String,Object>>();
for(int i = 0; i < seedloccfg.size(); i++) {
DynmapLocation dl = seedloccfg.get(i);
ConfigurationNode ll = new ConfigurationNode();
ll.put("x", dl.x);
ll.put("y", dl.y);
ll.put("z", dl.z);
locs.add(ll.entries);
}
node.put("fullrenderlocations", locs);
}
/* Add flags */
node.put("sendposition", sendposition);
node.put("sendhealth", sendhealth);
node.put("extrazoomout", extrazoomoutlevels);
/* Save visibility limits, if defined */
if(visibility_limits != null) {
ArrayList<Map<String,Object>> lims = new ArrayList<Map<String,Object>>();
for(int i = 0; i < visibility_limits.size(); i++) {
VisibilityLimit lim = visibility_limits.get(i);
LinkedHashMap<String, Object> lv = new LinkedHashMap<String,Object>();
if (lim instanceof RectangleVisibilityLimit) {
RectangleVisibilityLimit rect_lim = (RectangleVisibilityLimit) lim;
lv.put("x0", rect_lim.x_min);
lv.put("z0", rect_lim.z_min);
lv.put("x1", rect_lim.x_max);
lv.put("z1", rect_lim.z_max);
}
else {
RoundVisibilityLimit round_lim = (RoundVisibilityLimit) lim;
lv.put("x", round_lim.x_center);
lv.put("z", round_lim.z_center);
lv.put("r", round_lim.radius);
}
lims.add(lv);
}
node.put("visibilitylimits", lims);
}
/* Save hidden limits, if defined */
if(hidden_limits != null) {
ArrayList<Map<String,Object>> lims = new ArrayList<Map<String,Object>>();
for(int i = 0; i < hidden_limits.size(); i++) {
VisibilityLimit lim = hidden_limits.get(i);
LinkedHashMap<String, Object> lv = new LinkedHashMap<String,Object>();
if (lim instanceof RectangleVisibilityLimit) {
RectangleVisibilityLimit rect_lim = (RectangleVisibilityLimit) lim;
lv.put("x0", rect_lim.x_min);
lv.put("z0", rect_lim.z_min);
lv.put("x1", rect_lim.x_max);
lv.put("z1", rect_lim.z_max);
}
else {
RoundVisibilityLimit round_lim = (RoundVisibilityLimit) lim;
lv.put("x", round_lim.x_center);
lv.put("z", round_lim.z_center);
lv.put("r", round_lim.radius);
}
lims.add(lv);
}
node.put("hiddenlimits", lims);
}
/* Handle hide style */
node.put("hidestyle", hiddenchunkstyle.getValue());
/* Handle map settings */
ArrayList<Map<String,Object>> mapinfo = new ArrayList<Map<String,Object>>();
for(MapType mt : maps) {
ConfigurationNode mnode = mt.saveConfiguration();
mapinfo.add(mnode);
}
node.put("maps", mapinfo);
return node;
}
public boolean isEnabled() {
return is_enabled;
}
public void setTitle(String title) {
this.title = title;
}
public static String normalizeWorldName(String n) {
return (n != null)?n.replace('/', '-').replace('[', '_').replace(']', '_'):null;
}
public String getRawName() {
return raw_wname;
}
public boolean isProtected() {
return is_protected;
}
public int getTileUpdateDelay() {
if(tileupdatedelay > 0)
return tileupdatedelay;
else
return MapManager.mapman.getDefTileUpdateDelay();
}
public void setTileUpdateDelay(int time_sec) {
tileupdatedelay = time_sec;
}
public static void doInitialScan(boolean doscan) {
}
// Return number of chunks found (-1 if not implemented)
public int getChunkMap(TileFlags map) {
return -1;
}
// Get map state for given map
public MapTypeState getMapState(MapType m) {
for (int i = 0; i < this.maps.size(); i++) {
MapType mt = this.maps.get(i);
if (mt == m) {
return this.mapstate.get(i);
}
}
return null;
}
public void purgeTree() {
storage.purgeMapTiles(this, null);
}
public void purgeMap(MapType mt) {
storage.purgeMapTiles(this, mt);
}
public MapStorage getMapStorage() {
return storage;
}
public Polygon getWorldBorder() {
return null;
}
}

View file

@ -0,0 +1,62 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Event<T> {
private List<Listener<T>> listeners = new LinkedList<Listener<T>>();
private Object lock = new Object();
public void addListener(Listener<T> l) {
synchronized(lock) {
listeners.add(l);
}
}
public void removeListener(Listener<T> l) {
synchronized(lock) {
listeners.remove(l);
}
}
/* Only use from main thread */
public void trigger(T t) {
ArrayList<Listener<T>> iterlist;
synchronized(lock) {
iterlist = new ArrayList<Listener<T>>(listeners);
}
for (Listener<T> l : iterlist) {
l.triggered(t);
}
}
/* Trigger on main thread */
public boolean triggerSync(DynmapCore core, final T t) {
Future<T> future = core.getServer().callSyncMethod(new Callable<T>() {
@Override
public T call() throws Exception {
trigger(t);
return t;
}
});
boolean success = false;
try {
if(future != null) {
future.get();
success = true;
}
} catch (ExecutionException ix) {
Log.severe("Exception in triggerSync", ix.getCause());
} catch (InterruptedException ix) {
}
return success;
}
public interface Listener<T> {
void triggered(T t);
}
}

View file

@ -0,0 +1,44 @@
package org.dynmap;
import java.util.HashMap;
import java.util.Map;
public class Events {
public Map<String, Event<?>> events = new HashMap<String, Event<?>>();
@SuppressWarnings("unchecked")
public <T> void addListener(String eventName, Event.Listener<T> listener) {
Event<?> genericEvent = events.get(eventName);
Event<T> event = null;
if (genericEvent != null) {
event = (Event<T>)genericEvent;
} else {
events.put(eventName, event = new Event<T>());
}
event.addListener(listener);
}
@SuppressWarnings("unchecked")
public <T> void removeListener(String eventName, Event.Listener<T> listener) {
Event<?> genericEvent = events.get(eventName);
Event<T> event = null;
if (genericEvent != null) {
event = (Event<T>)genericEvent;
event.removeListener(listener);
}
}
@SuppressWarnings("unchecked")
public <T> void trigger(String eventName, T argument) {
Event<?> genericEvent = events.get(eventName);
if (genericEvent == null)
return;
((Event<T>)genericEvent).trigger(argument);
}
@SuppressWarnings("unchecked")
public <T> void triggerSync(DynmapCore core, String eventName, T argument) {
Event<?> genericEvent = events.get(eventName);
if (genericEvent == null)
return;
((Event<T>)genericEvent).triggerSync(core, argument);
}
}

View file

@ -0,0 +1,5 @@
package org.dynmap;
public interface Handler<T> {
void handle(T t);
}

View file

@ -0,0 +1,154 @@
package org.dynmap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.dynmap.servlet.ClientUpdateServlet;
import org.dynmap.servlet.SendMessageServlet;
import org.dynmap.utils.IpAddressMatcher;
import org.json.simple.JSONObject;
import static org.dynmap.JSONUtils.*;
public class InternalClientUpdateComponent extends ClientUpdateComponent {
protected long jsonInterval;
protected long currentTimestamp = 0;
protected long lastTimestamp = 0;
protected long lastChatTimestamp = 0;
private long last_confighash;
private ConcurrentHashMap<String, JSONObject> updates = new ConcurrentHashMap<String, JSONObject>();
private JSONObject clientConfiguration = null;
private static InternalClientUpdateComponent singleton;
public InternalClientUpdateComponent(final DynmapCore dcore, final ConfigurationNode configuration) {
super(dcore, configuration);
dcore.addServlet("/up/world/*", new ClientUpdateServlet(dcore));
if (dcore.isInternalWebServerDisabled) {
Log.severe("Using InternalClientUpdateComponent with disable-webserver=true is not supported: there will likely be problems");
}
jsonInterval = (long)(configuration.getFloat("writeinterval", 1) * 1000);
final Boolean allowwebchat = configuration.getBoolean("allowwebchat", false);
final Boolean hidewebchatip = configuration.getBoolean("hidewebchatip", false);
final Boolean trust_client_name = configuration.getBoolean("trustclientname", false);
final float webchatInterval = configuration.getFloat("webchat-interval", 1);
final String spammessage = dcore.configuration.getString("spammessage", "You may only chat once every %interval% seconds.");
final Boolean use_player_ip = configuration.getBoolean("use-player-login-ip", true);
final Boolean req_player_ip = configuration.getBoolean("require-player-login-ip", false);
final Boolean block_banned_player_chat = configuration.getBoolean("block-banned-player-chat", false);
final Boolean req_login = configuration.getBoolean("webchat-requires-login", false);
final Boolean chat_perm = configuration.getBoolean("webchat-permissions", false);
final int length_limit = configuration.getInteger("chatlengthlimit", 256);
final List<String> trustedproxy = dcore.configuration.getStrings("trusted-proxies", null);
dcore.events.addListener("buildclientconfiguration", new Event.Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
s(t, "allowwebchat", allowwebchat);
s(t, "webchat-interval", webchatInterval);
s(t, "webchat-requires-login", req_login);
s(t, "chatlengthlimit", length_limit);
}
});
if (allowwebchat) {
@SuppressWarnings("serial")
SendMessageServlet messageHandler = new SendMessageServlet() {{
maximumMessageInterval = (int)(webchatInterval * 1000);
spamMessage = "\""+spammessage+"\"";
hideip = hidewebchatip;
this.trustclientname = trust_client_name;
this.use_player_login_ip = use_player_ip;
this.require_player_login_ip = req_player_ip;
this.check_user_ban = block_banned_player_chat;
this.require_login = req_login;
this.chat_perms = chat_perm;
this.lengthlimit = length_limit;
this.core = dcore;
if(trustedproxy != null) {
for(String s : trustedproxy) {
this.proxyaddress.add(new IpAddressMatcher(s.trim()));
}
}
else {
this.proxyaddress.add(new IpAddressMatcher("127.0.0.1"));
this.proxyaddress.add(new IpAddressMatcher("0:0:0:0:0:0:0:1"));
}
onMessageReceived.addListener(new Event.Listener<Message> () {
@Override
public void triggered(Message t) {
core.webChat(t.name, t.message);
}
});
}};
dcore.addServlet("/up/sendmessage", messageHandler);
}
core.getServer().scheduleServerTask(new Runnable() {
@Override
public void run() {
currentTimestamp = System.currentTimeMillis();
if(last_confighash != core.getConfigHashcode()) {
writeConfiguration();
}
writeUpdates();
// if (allowwebchat) {
// handleWebChat();
// }
// if(core.isLoginSupportEnabled())
// handleRegister();
lastTimestamp = currentTimestamp;
core.getServer().scheduleServerTask(this, jsonInterval/50);
}}, jsonInterval/50);
core.events.addListener("initialized", new Event.Listener<Object>() {
@Override
public void triggered(Object t) {
writeConfiguration();
writeUpdates(); /* Make sure we stay in sync */
}
});
core.events.addListener("worldactivated", new Event.Listener<DynmapWorld>() {
@Override
public void triggered(DynmapWorld t) {
writeConfiguration();
writeUpdates(); /* Make sure we stay in sync */
}
});
/* Initialize */
writeConfiguration();
writeUpdates();
singleton = this;
}
@SuppressWarnings("unchecked")
protected void writeUpdates() {
if(core.mapManager == null) return;
//Handles Updates
for (DynmapWorld dynmapWorld : core.mapManager.getWorlds()) {
JSONObject update = new JSONObject();
update.put("timestamp", currentTimestamp);
ClientUpdateEvent clientUpdate = new ClientUpdateEvent(currentTimestamp - 30000, dynmapWorld, update);
clientUpdate.include_all_users = true;
core.events.trigger("buildclientupdate", clientUpdate);
updates.put(dynmapWorld.getName(), update);
}
}
protected void writeConfiguration() {
JSONObject clientConfiguration = new JSONObject();
core.events.trigger("buildclientconfiguration", clientConfiguration);
this.clientConfiguration = clientConfiguration;
last_confighash = core.getConfigHashcode();
}
public static JSONObject getWorldUpdate(String wname) {
if(singleton != null) {
return singleton.updates.get(wname);
}
return null;
}
public static JSONObject getClientConfig() {
if(singleton != null)
return singleton.clientConfiguration;
return null;
}
}

View file

@ -0,0 +1,72 @@
package org.dynmap;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class JSONUtils {
// Gets a value at the specified path.
public static Object g(JSONObject o, String path) {
int index = path.indexOf('/');
if (index == -1) {
return o.get(path);
} else {
String key = path.substring(0, index);
String subpath = path.substring(index+1);
Object oo = o.get(key);
JSONObject subobject;
if (oo == null) {
return null;
} else /*if (oo instanceof JSONObject)*/ {
subobject = (JSONObject)o;
}
return g(subobject, subpath);
}
}
// Sets a value on the specified path. If JSONObjects inside the path are missing, they'll be created.
@SuppressWarnings("unchecked")
public static void s(JSONObject o, String path, Object value) {
int index = path.indexOf('/');
if (index == -1) {
o.put(path, value);
} else {
String key = path.substring(0, index);
String subpath = path.substring(index+1);
Object oo = o.get(key);
JSONObject subobject;
if (oo == null) {
subobject = new JSONObject();
o.put(key, subobject);
} else /*if (oo instanceof JSONObject)*/ {
subobject = (JSONObject)oo;
}
s(subobject, subpath, value);
}
}
// Adds a value to the list at the specified path. If the list does not exist, it will be created.
@SuppressWarnings("unchecked")
public static void a(JSONObject o, String path, Object value) {
Object oo = g(o, path);
JSONArray array;
if (oo == null) {
array =new JSONArray();
s(o, path, array);
} else {
array = (JSONArray)oo;
}
if(value != null)
array.add(value);
}
// Simply creates a JSONArray.
@SuppressWarnings("unchecked")
public static JSONArray l(Object... items) {
JSONArray arr = new JSONArray();
for(Object item : items) {
arr.add(item);
}
return arr;
}
}

View file

@ -0,0 +1,559 @@
package org.dynmap;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.dynmap.storage.MapStorage;
import org.dynmap.utils.BufferInputStream;
import org.dynmap.utils.BufferOutputStream;
import org.dynmap.web.Json;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import static org.dynmap.JSONUtils.*;
import java.nio.charset.Charset;
import java.util.concurrent.CompletableFuture;
public class JsonFileClientUpdateComponent extends ClientUpdateComponent {
protected long jsonInterval;
protected long currentTimestamp = 0;
protected long lastTimestamp = 0;
protected long lastChatTimestamp = 0;
protected JSONParser parser = new JSONParser();
private boolean hidewebchatip;
private boolean useplayerloginip;
private boolean requireplayerloginip;
private boolean trust_client_name;
private boolean checkuserban;
private boolean req_login;
private boolean chat_perms;
private int lengthlimit;
private HashMap<String,String> useralias = new HashMap<String,String>();
private int aliasindex = 1;
private long last_confighash;
private MessageDigest md;
private MapStorage storage;
private File baseStandaloneDir;
private static class FileToWrite {
String filename;
byte[] content;
boolean phpwrapper;
@Override
public boolean equals(Object o) {
if(o instanceof FileToWrite) {
return ((FileToWrite)o).filename.equals(this.filename);
}
return false;
}
}
private class FileProcessor implements Runnable {
public void run() {
while(true) {
FileToWrite f = null;
synchronized(lock) {
if(files_to_write.isEmpty() == false) {
f = files_to_write.removeFirst();
}
else {
pending = null;
return;
}
}
BufferOutputStream buf = null;
if (f.content != null) {
buf = new BufferOutputStream();
if(f.phpwrapper) {
buf.write("<?php /*\n".getBytes(cs_utf8));
}
buf.write(f.content);
if(f.phpwrapper) {
buf.write("\n*/ ?>\n".getBytes(cs_utf8));
}
}
if (!storage.setStandaloneFile(f.filename, buf)) {
Log.severe("Exception while writing JSON-file - " + f.filename);
}
}
}
}
private Object lock = new Object();
private FileProcessor pending;
private LinkedList<FileToWrite> files_to_write = new LinkedList<FileToWrite>();
private void enqueueFileWrite(String filename, byte[] content, boolean phpwrap) {
FileToWrite ftw = new FileToWrite();
ftw.filename = filename;
ftw.content = content;
ftw.phpwrapper = phpwrap;
synchronized(lock) {
boolean didadd = false;
if(pending == null) {
didadd = true;
pending = new FileProcessor();
}
files_to_write.remove(ftw);
files_to_write.add(ftw);
if(didadd) {
MapManager.scheduleDelayedJob(new FileProcessor(), 0);
}
}
}
private static Charset cs_utf8 = Charset.forName("UTF-8");
public JsonFileClientUpdateComponent(final DynmapCore core, final ConfigurationNode configuration) {
super(core, configuration);
if (!core.isInternalWebServerDisabled) {
Log.severe("Using JsonFileClientUpdateComponent with disable-webserver=false is not supported: there will likely be problems");
}
final boolean allowwebchat = configuration.getBoolean("allowwebchat", false);
jsonInterval = (long)(configuration.getFloat("writeinterval", 1) * 1000);
hidewebchatip = configuration.getBoolean("hidewebchatip", false);
useplayerloginip = configuration.getBoolean("use-player-login-ip", true);
requireplayerloginip = configuration.getBoolean("require-player-login-ip", false);
trust_client_name = configuration.getBoolean("trustclientname", false);
checkuserban = configuration.getBoolean("block-banned-player-chat", true);
req_login = configuration.getBoolean("webchat-requires-login", false);
chat_perms = configuration.getBoolean("webchat-permissions", false);
lengthlimit = configuration.getInteger("chatlengthlimit", 256);
storage = core.getDefaultMapStorage();
baseStandaloneDir = new File(core.configuration.getString("webpath", "web"), "standalone");
if (!baseStandaloneDir.isAbsolute()) {
baseStandaloneDir = new File(core.getDataFolder(), baseStandaloneDir.toString());
}
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException nsax) {
Log.severe("Unable to get message digest SHA-1");
}
/* Generate our config.js file */
generateConfigJS(core);
core.getServer().scheduleServerTask(new Runnable() {
@Override
public void run() {
currentTimestamp = System.currentTimeMillis();
if(last_confighash != core.getConfigHashcode()) {
writeConfiguration();
}
writeUpdates();
if (allowwebchat) {
handleWebChat();
}
if(core.isLoginSupportEnabled())
handleRegister();
lastTimestamp = currentTimestamp;
core.getServer().scheduleServerTask(this, jsonInterval/50);
}}, jsonInterval/50);
core.events.addListener("buildclientconfiguration", new Event.Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
s(t, "jsonfile", true);
s(t, "allowwebchat", allowwebchat);
s(t, "webchat-requires-login", req_login);
s(t, "loginrequired", core.isLoginRequired());
// For 'sendmessage.php'
s(t, "webchat-interval", configuration.getFloat("webchat-interval", 5.0f));
s(t, "chatlengthlimit", lengthlimit);
}
});
core.events.addListener("initialized", new Event.Listener<Object>() {
@Override
public void triggered(Object t) {
writeConfiguration();
writeUpdates(); /* Make sure we stay in sync */
writeLogins();
writeAccess();
}
});
core.events.addListener("server-started", new Event.Listener<Object>() {
@Override
public void triggered(Object t) {
writeConfiguration();
writeUpdates(); /* Make sure we stay in sync */
writeLogins();
writeAccess();
}
});
core.events.addListener("worldactivated", new Event.Listener<DynmapWorld>() {
@Override
public void triggered(DynmapWorld t) {
writeConfiguration();
writeUpdates(); /* Make sure we stay in sync */
writeAccess();
}
});
core.events.addListener("loginupdated", new Event.Listener<Object>() {
@Override
public void triggered(Object t) {
writeLogins();
writeAccess();
}
});
core.events.addListener("playersetupdated", new Event.Listener<Object>() {
@Override
public void triggered(Object t) {
writeAccess();
}
});
}
private void generateConfigJS(DynmapCore core) {
/* Test if login support is enabled */
boolean login_enabled = core.isLoginSupportEnabled();
// configuration: 'standalone/dynmap_config.json?_={timestamp}',
// update: 'standalone/dynmap_{world}.json?_={timestamp}',
// sendmessage: 'standalone/sendmessage.php',
// login: 'standalone/login.php',
// register: 'standalone/register.php',
// tiles : 'tiles/',
// markers : 'tiles/'
// configuration: 'standalone/configuration.php',
// update: 'standalone/update.php?world={world}&ts={timestamp}',
// sendmessage: 'standalone/sendmessage.php',
// login: 'standalone/login.php',
// register: 'standalone/register.php',
// tiles : 'standalone/tiles.php?tile=',
// markers : 'standalone/markers.php?marker='
MapStorage store = core.getDefaultMapStorage();
StringBuilder sb = new StringBuilder();
sb.append("var config = {\n");
sb.append(" url : {\n");
/* Get configuration URL */
sb.append(" configuration: '");
sb.append(core.configuration.getString("url/configuration", store.getConfigurationJSONURI(login_enabled)));
sb.append("',\n");
/* Get update URL */
sb.append(" update: '");
sb.append(core.configuration.getString("url/update", store.getUpdateJSONURI(login_enabled)));
sb.append("',\n");
/* Get sendmessage URL */
sb.append(" sendmessage: '");
sb.append(core.configuration.getString("url/sendmessage", store.getSendMessageURI()));
sb.append("',\n");
/* Get login URL */
sb.append(" login: '");
sb.append(core.configuration.getString("url/login", store.getStandaloneLoginURI()));
sb.append("',\n");
/* Get register URL */
sb.append(" register: '");
sb.append(core.configuration.getString("url/register", store.getStandaloneRegisterURI()));
sb.append("',\n");
/* Get tiles URL */
sb.append(" tiles: '");
sb.append(core.configuration.getString("url/tiles", store.getTilesURI(login_enabled)));
sb.append("',\n");
/* Get markers URL */
sb.append(" markers: '");
sb.append(core.configuration.getString("url/markers", store.getMarkersURI(login_enabled)));
sb.append("'\n }\n};\n");
byte[] outputBytes = sb.toString().getBytes(cs_utf8);
MapManager.scheduleDelayedJob(new Runnable() {
public void run() {
if (core.getDefaultMapStorage().needsStaticWebFiles()) {
BufferOutputStream os = new BufferOutputStream();
os.write(outputBytes);
core.getDefaultMapStorage().setStaticWebFile("standalone/config.js", os);
}
else {
File f = new File(baseStandaloneDir, "config.js");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
fos.write(outputBytes);
} catch (IOException iox) {
Log.severe("Exception while writing " + f.getPath(), iox);
} finally {
if(fos != null) {
try {
fos.close();
} catch (IOException x) {}
fos = null;
}
}
}
}
}, 0);
}
protected void writeConfiguration() {
JSONObject clientConfiguration = new JSONObject();
core.events.trigger("buildclientconfiguration", clientConfiguration);
last_confighash = core.getConfigHashcode();
byte[] content = clientConfiguration.toJSONString().getBytes(cs_utf8);
String outputFile;
boolean dowrap = storage.wrapStandaloneJSON(core.isLoginSupportEnabled());
if(dowrap) {
outputFile = "dynmap_config.php";
}
else {
outputFile = "dynmap_config.json";
}
enqueueFileWrite(outputFile, content, dowrap);
}
@SuppressWarnings("unchecked")
protected void writeUpdates() {
if(core.mapManager == null) return;
//Handles Updates
ArrayList<DynmapWorld> wlist = new ArrayList<DynmapWorld>(core.mapManager.getWorlds()); // Grab copy of world list
for (int windx = 0; windx < wlist.size(); windx++) {
DynmapWorld dynmapWorld = wlist.get(windx);
JSONObject update = new JSONObject();
update.put("timestamp", currentTimestamp);
ClientUpdateEvent clientUpdate = new ClientUpdateEvent(currentTimestamp - 30000, dynmapWorld, update);
clientUpdate.include_all_users = true;
core.events.trigger("buildclientupdate", clientUpdate);
String outputFile;
boolean dowrap = storage.wrapStandaloneJSON(core.isLoginSupportEnabled());
if(dowrap) {
outputFile = "updates_" + dynmapWorld.getName() + ".php";
}
else {
outputFile = "dynmap_" + dynmapWorld.getName() + ".json";
}
CompletableFuture.runAsync(() -> {
byte[] content = Json.stringifyJson(update).getBytes(cs_utf8);
enqueueFileWrite(outputFile, content, dowrap);
});
}
}
private byte[] loginhash = new byte[16];
protected void writeLogins() {
String loginFile = "dynmap_login.php";
if(core.isLoginSupportEnabled()) {
String s = core.getLoginPHP(storage.wrapStandalonePHP());
if(s != null) {
byte[] bytes = s.getBytes(cs_utf8);
md.reset();
byte[] hash = md.digest(bytes);
if(Arrays.equals(hash, loginhash)) {
return;
}
enqueueFileWrite(loginFile, bytes, false);
loginhash = hash;
}
}
else {
enqueueFileWrite(loginFile, null, false);
}
}
private byte[] accesshash = new byte[16];
protected void writeAccess() {
String accessFile = "dynmap_access.php";
String s = core.getAccessPHP(storage.wrapStandalonePHP());
if(s != null) {
byte[] bytes = s.getBytes(cs_utf8);
md.reset();
byte[] hash = md.digest(bytes);
if(Arrays.equals(hash, accesshash)) {
return;
}
enqueueFileWrite(accessFile, bytes, false);
accesshash = hash;
}
}
private void processWebChat(JSONArray jsonMsgs) {
Iterator<?> iter = jsonMsgs.iterator();
boolean init_skip = (lastChatTimestamp == 0);
while (iter.hasNext()) {
boolean ok = true;
JSONObject o = (JSONObject) iter.next();
String ts = String.valueOf(o.get("timestamp"));
if(ts.equals("null")) ts = "0";
long cts;
try {
cts = Long.parseLong(ts);
} catch (NumberFormatException nfx) {
try {
cts = (long) Double.parseDouble(ts);
} catch (NumberFormatException nfx2) {
cts = 0;
}
}
if (cts > lastChatTimestamp) {
String name = String.valueOf(o.get("name"));
String ip = String.valueOf(o.get("ip"));
String uid = null;
Object usr = o.get("userid");
if(usr != null) {
uid = String.valueOf(usr);
}
boolean isip = true;
lastChatTimestamp = cts;
if(init_skip)
continue;
if(uid == null) {
if((!trust_client_name) || (name == null) || (name.equals(""))) {
if(ip != null)
name = ip;
}
if(useplayerloginip) { /* Try to match using IPs of player logins */
List<String> ids = core.getIDsForIP(name);
if(ids != null && !ids.isEmpty()) {
name = ids.get(0);
isip = false;
if(checkuserban) {
if(core.getServer().isPlayerBanned(name)) {
Log.info("Ignore message from '" + ip + "' - banned player (" + name + ")");
ok = false;
}
}
if(chat_perms && !core.getServer().checkPlayerPermission(name, "webchat")) {
Log.info("Rejected web chat from " + ip + ": not permitted (" + name + ")");
ok = false;
}
}
else if(requireplayerloginip) {
Log.info("Ignore message from '" + name + "' - no matching player login recorded");
ok = false;
}
}
if(hidewebchatip && isip) {
String n = useralias.get(name);
if(n == null) { /* Make ID */
n = String.format("web-%03d", aliasindex);
aliasindex++;
useralias.put(name, n);
}
name = n;
}
}
else {
if(core.getServer().isPlayerBanned(uid)) {
Log.info("Ignore message from '" + uid + "' - banned user");
ok = false;
}
if(chat_perms && !core.getServer().checkPlayerPermission(uid, "webchat")) {
Log.info("Rejected web chat from " + uid + ": not permitted");
ok = false;
}
name = uid;
}
if(ok) {
String message = String.valueOf(o.get("message"));
if((lengthlimit > 0) && (message.length() > lengthlimit))
message = message.substring(0, lengthlimit);
core.webChat(name, message);
}
}
}
}
protected void handleWebChat() {
MapManager.scheduleDelayedJob(new Runnable() {
public void run() {
BufferInputStream bis = storage.getStandaloneFile("dynmap_webchat.json");
if (bis != null && lastTimestamp != 0) {
JSONArray jsonMsgs = null;
Reader inputFileReader = null;
try {
inputFileReader = new InputStreamReader(bis, cs_utf8);
jsonMsgs = (JSONArray) parser.parse(inputFileReader);
} catch (IOException ex) {
Log.severe("Exception while reading JSON-file.", ex);
storage.setStandaloneFile("dynmap_webchat.json", null); // Delete it
} catch (ParseException ex) {
Log.severe("Exception while parsing JSON-file.", ex);
storage.setStandaloneFile("dynmap_webchat.json", null); // Delete it
} finally {
if(inputFileReader != null) {
try {
inputFileReader.close();
} catch (IOException iox) {
}
inputFileReader = null;
}
}
if (jsonMsgs != null) {
final JSONArray json = jsonMsgs;
// Process content on server thread
core.getServer().scheduleServerTask(new Runnable() {
@Override
public void run() {
processWebChat(json);
}
}, 0);
}
}
}
}, 0);
}
protected void handleRegister() {
if(core.pendingRegisters() == false)
return;
BufferInputStream bis = storage.getStandaloneFile("dynmap_reg.php");
if (bis != null) {
BufferedReader br = null;
ArrayList<String> lines = new ArrayList<String>();
try {
br = new BufferedReader(new InputStreamReader(bis));
String line;
while ((line = br.readLine()) != null) {
if(line.startsWith("<?") || line.startsWith("*/")) {
continue;
}
lines.add(line);
}
} catch (IOException iox) {
Log.severe("Exception while reading dynmap_reg.php", iox);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException x) {
}
br = null;
}
}
for(int i = 0; i < lines.size(); i++) {
String[] vals = lines.get(i).split("=");
if(vals.length == 3) {
core.processCompletedRegister(vals[0].trim(), vals[1].trim(), vals[2].trim());
}
}
}
}
@Override
public void dispose() {
super.dispose();
}
}

View file

@ -0,0 +1,93 @@
package org.dynmap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dynmap.utils.DynmapLogger;
public class Log {
private static Logger log = Logger.getLogger("Dynmap");
private static String prefix = "";
private static DynmapLogger dlog = null;
public static boolean verbose = false;
public static String safeString(String s) { return s.replaceAll("[\\${}]", "_"); }
public static void setLogger(Logger logger, String pre) {
log = logger;
if((pre != null) && (pre.length() > 0))
prefix = pre + " ";
else
prefix = "";
}
public static void setLogger(DynmapLogger logger) {
dlog = logger;
}
public static void setLoggerParent(Logger parent) {
log.setParent(parent);
}
public static void info(String msg) {
msg = safeString(msg);
if (dlog != null) {
dlog.info(msg);
}
else {
log.log(Level.INFO, prefix + msg);
}
}
public static void verboseinfo(String msg) {
if(verbose) {
msg = safeString(msg);
if (dlog != null) {
dlog.info(msg);
}
else {
log.log(Level.INFO, prefix + msg);
}
}
}
public static void severe(Throwable e) {
if (dlog != null) {
dlog.severe(e);
}
else {
log.log(Level.SEVERE, prefix + "Exception occured: ", e);
}
}
public static void severe(String msg) {
msg = safeString(msg);
if (dlog != null) {
dlog.severe(msg);
}
else {
log.log(Level.SEVERE, prefix + msg);
}
}
public static void severe(String msg, Throwable e) {
msg = safeString(msg);
if (dlog != null) {
dlog.severe(msg, e);
}
else {
log.log(Level.SEVERE, prefix + msg, e);
}
}
public static void warning(String msg) {
msg = safeString(msg);
if (dlog != null) {
dlog.warning(msg);
}
else {
log.log(Level.WARNING, prefix + msg);
}
}
public static void warning(String msg, Throwable e) {
msg = safeString(msg);
if (dlog != null) {
dlog.warning(msg, e);
}
else {
log.log(Level.WARNING, prefix + msg, e);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
package org.dynmap;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.dynmap.utils.MapChunkCache;
public abstract class MapTile {
protected DynmapWorld world;
public abstract boolean render(MapChunkCache cache, String mapname);
public abstract List<DynmapChunk> getRequiredChunks();
public abstract MapTile[] getAdjecentTiles();
public abstract int getTileSize();
public DynmapWorld getDynmapWorld() {
return world;
}
public MapTile(DynmapWorld world) {
this.world = world;
}
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
public abstract boolean isBiomeDataNeeded();
public abstract boolean isHightestBlockYDataNeeded();
public abstract boolean isRawBiomeDataNeeded();
public abstract boolean isBlockTypeDataNeeded();
public abstract int tileOrdinalX();
public abstract int tileOrdinalY();
public ConfigurationNode saveTile() {
ConfigurationNode cn = new ConfigurationNode();
cn.put("class", this.getClass().getName());
cn.put("data", saveTileData());
return cn;
}
protected abstract String saveTileData();
public static MapTile restoreTile(DynmapWorld w, ConfigurationNode node) {
String cn = node.getString("class");
String dat = node.getString("data");
if((cn == null) || (dat == null)) return null;
try {
Class<?> cls = Class.forName(cn);
Constructor<?> con = cls.getConstructor(DynmapWorld.class, String.class);
return (MapTile)con.newInstance(w, dat);
} catch (ClassNotFoundException cnfx) {
} catch (NoSuchMethodException nsmx) {
} catch (InvocationTargetException itx) {
} catch (IllegalAccessException iax) {
} catch (InstantiationException ix) {
}
return null;
}
}

View file

@ -0,0 +1,254 @@
package org.dynmap;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.dynmap.utils.TileFlags;
import org.json.simple.JSONObject;
public abstract class MapType {
private boolean is_protected;
/**
* Is the map type read-only? (i.e. should not be updated by renderer)
*/
private boolean is_readonly;
protected int tileupdatedelay;
public enum ImageVariant {
STANDARD(""), // Typical image
DAY("day"); // Day (no shadow) image
public final String variantSuffix;
public final String variantID;
ImageVariant(String varid) {
if (varid.length() > 0) {
variantSuffix = "_" + varid;
}
else {
variantSuffix = "";
}
variantID = varid;
}
}
public enum ImageEncoding {
PNG("png", "image/png", true), JPG("jpg", "image/jpeg", false), WEBP("webp", "image/webp", true);
public final String ext;
public final String mimetype;
public final boolean hasAlpha;
ImageEncoding(String ext, String mime, boolean has_alpha) {
this.ext = ext;
this.mimetype = mime;
this.hasAlpha = has_alpha;
}
public String getFileExt() { return ext; }
public String getContentType() { return mimetype; }
public static ImageEncoding fromOrd(int ix) {
ImageEncoding[] v = values();
if ((ix >= 0) && (ix < v.length))
return v[ix];
return null;
}
public static ImageEncoding fromContentType(String ct) {
ImageEncoding[] v = values();
for (int i = 0; i < v.length; i++) {
if (v[i].mimetype.equalsIgnoreCase(ct)) {
return v[i];
}
}
return null;
}
public static ImageEncoding fromExt(String x) {
ImageEncoding[] v = values();
for (int i = 0; i < v.length; i++) {
if (v[i].ext.equalsIgnoreCase(x)) {
return v[i];
}
}
return null;
}
}
public enum ImageFormat {
FORMAT_PNG("png", 0.0f, ImageEncoding.PNG),
FORMAT_JPG75("jpg-q75", 0.75f, ImageEncoding.JPG),
FORMAT_JPG80("jpg-q80", 0.80f, ImageEncoding.JPG),
FORMAT_JPG85("jpg-q85", 0.85f, ImageEncoding.JPG),
FORMAT_JPG("jpg", 0.85f, ImageEncoding.JPG),
FORMAT_JPG90("jpg-q90", 0.90f, ImageEncoding.JPG),
FORMAT_JPG95("jpg-q95", 0.95f, ImageEncoding.JPG),
FORMAT_JPG100("jpg-q100", 1.00f, ImageEncoding.JPG),
FORMAT_WEBP75("webp-q75", 75, ImageEncoding.WEBP),
FORMAT_WEBP80("webp-q80", 80, ImageEncoding.WEBP),
FORMAT_WEBP85("webp-q85", 85, ImageEncoding.WEBP),
FORMAT_WEBP("webp", 85, ImageEncoding.WEBP),
FORMAT_WEBP90("webp-q90", 90, ImageEncoding.WEBP),
FORMAT_WEBP95("webp-q95", 95, ImageEncoding.WEBP),
FORMAT_WEBP100("webp-q100", 100, ImageEncoding.WEBP),
FORMAT_WEBPL("webp-l", 85, ImageEncoding.WEBP);
String id;
float qual;
ImageEncoding enc;
ImageFormat(String id, float quality, ImageEncoding enc) {
this.id = id;
this.qual = quality;
this.enc = enc;
}
public String getID() { return id; }
public String getFileExt() { return enc.getFileExt(); }
public float getQuality() { return qual; }
public ImageEncoding getEncoding() { return enc; }
public static ImageFormat fromID(String imgfmt) {
for(ImageFormat i_f : MapType.ImageFormat.values()) {
if(i_f.getID().equals(imgfmt)) {
return i_f;
}
}
return null;
}
};
public static class ZoomInfo {
public String prefix;
public int background_argb;
public ZoomInfo(String pre, int bg) { prefix = pre; background_argb = bg; }
}
public abstract void addMapTiles(List<MapTile> list, DynmapWorld w, int tx, int ty);
public abstract List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int x, int y, int z);
public abstract List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int minx, int miny, int minz, int maxx, int maxy, int maxz);
public abstract MapTile[] getAdjecentTiles(MapTile tile);
public abstract List<DynmapChunk> getRequiredChunks(MapTile tile);
public abstract int getTileSize();
public void buildClientConfiguration(JSONObject worldObject, DynmapWorld w) {
}
public List<MapTile> getTiles(DynmapWorld w, int x, int y, int z) {
List<TileFlags.TileCoord> coords = this.getTileCoords(w, x, y, z);
ArrayList<MapTile> tiles = new ArrayList<MapTile>();
for(TileFlags.TileCoord c : coords) {
this.addMapTiles(tiles, w, c.x, c.y);
}
return tiles;
}
public abstract String getName();
/* Get maps rendered concurrently with this map in this world */
public abstract List<MapType> getMapsSharingRender(DynmapWorld w);
/* Get names of maps rendered concurrently with this map type in this world */
public abstract List<String> getMapNamesSharingRender(DynmapWorld w);
/* Return number of zoom levels needed by this map (before extra levels from extrazoomout) */
public int getMapZoomOutLevels() { return 0; }
public ImageFormat getImageFormat() { return ImageFormat.FORMAT_PNG; }
public int getBackgroundARGBNight() { return 0; }
public int getBackgroundARGBDay() { return 0; }
public int getBackgroundARGB(ImageVariant var) {
if (var == ImageVariant.DAY)
return getBackgroundARGBDay();
else
return getBackgroundARGBNight();
}
public void purgeOldTiles(DynmapWorld world, TileFlags rendered) { }
public interface FileCallback {
public void fileFound(File f, File parent, boolean day);
}
protected void walkMapTree(File root, FileCallback cb, boolean day) {
LinkedList<File> dirs = new LinkedList<File>();
String ext = "." + getImageFormat().getFileExt();
dirs.add(root);
while(dirs.isEmpty() == false) {
File dir = dirs.pop();
String[] lst = dir.list();
if(lst == null) continue;
for(String fn : lst) {
if(fn.equals(".") || fn.equals(".."))
continue;
File f = new File(dir, fn);
if(f.isDirectory()) { /* If directory, add to list to process */
dirs.add(f);
}
else if(fn.endsWith(ext)) { /* Else, if matches suffix */
cb.fileFound(f, dir, day);
}
}
}
}
public ConfigurationNode saveConfiguration() {
ConfigurationNode cn = new ConfigurationNode();
cn.put("class", this.getClass().getName()); /* Add class */
cn.put("name", getName()); /* Get map name */
return cn;
}
public boolean isProtected() {
return is_protected;
}
public boolean setProtected(boolean p) {
if(is_protected != p) {
is_protected = p;
return true;
}
return false;
}
/**
* Is the map type read-only? (i.e. should not be updated by renderer)
* @return true if read-only
*/
public boolean isReadOnly() {
return is_readonly;
}
/**
* Set read-only state of map type
* @param r - true if read-only
* @return true if state changed
*/
public boolean setReadOnly(boolean r) {
if(is_readonly != r) {
is_readonly = r;
return true;
}
return false;
}
public abstract String getPrefix();
public int getTileUpdateDelay(DynmapWorld w) {
if(tileupdatedelay > 0)
return tileupdatedelay;
else
return w.getTileUpdateDelay();
}
public boolean setTileUpdateDelay(int delay) {
if(tileupdatedelay != delay) {
tileupdatedelay = delay;
return true;
}
return false;
}
private static final ImageVariant[] defVariant = { ImageVariant.STANDARD };
public ImageVariant[] getVariants() {
return defVariant;
}
}

View file

@ -0,0 +1,277 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.dynmap.utils.TileFlags;
public class MapTypeState {
public static final long DEF_INV_PERIOD = 30;
public static final long NANOS_PER_SECOND = 1000000000L;
public MapType type;
private Object invTileLock = new Object();
private TileFlags pendingInvTiles = new TileFlags();
private TileFlags pendingInvTilesAlt = new TileFlags();
private TileFlags invTiles = new TileFlags();
private TileFlags.Iterator invTilesIter = invTiles.getIterator();
private long nextInvTS;
private long invTSPeriod;
private ArrayList<TileFlags> zoomOutInvAccum = new ArrayList<TileFlags>();
private ArrayList<TileFlags> zoomOutInv = new ArrayList<TileFlags>();
private TileFlags.Iterator zoomOutInvIter = null;
private int zoomOutInvIterLevel = -1;
private final int zoomOutLevels;
public final int tileSize;
public MapTypeState(DynmapWorld world, MapType mt) {
type = mt;
invTSPeriod = DEF_INV_PERIOD * NANOS_PER_SECOND;
nextInvTS = System.nanoTime() + invTSPeriod;
zoomOutLevels = world.getExtraZoomOutLevels() + mt.getMapZoomOutLevels();
for (int i = 0; i < zoomOutLevels; i++) {
zoomOutInv.add(null);
zoomOutInvAccum.add(null);
}
tileSize = mt.getTileSize();
}
public void setInvalidatePeriod(long inv_per_in_secs) {
invTSPeriod = inv_per_in_secs * NANOS_PER_SECOND;
}
public boolean invalidateTile(int tx, int ty) {
boolean done;
synchronized(invTileLock) {
done = !pendingInvTiles.setFlag(tx, ty, true);
}
return done;
}
public int invalidateTiles(List<TileFlags.TileCoord> coords) {
int cnt = 0;
synchronized(invTileLock) {
for(TileFlags.TileCoord c : coords) {
if(!pendingInvTiles.setFlag(c.x, c.y, true)) {
cnt++;
}
}
}
return cnt;
}
public void tickMapTypeState(long now_nano) {
if(nextInvTS < now_nano) {
synchronized(invTileLock) {
TileFlags tmp = pendingInvTilesAlt;
pendingInvTilesAlt = pendingInvTiles;
pendingInvTiles = tmp;
invTiles.union(tmp);
tmp.clear();
nextInvTS = now_nano + invTSPeriod;
}
}
}
public boolean getNextInvalidTileCoord(TileFlags.TileCoord coord) {
boolean match;
synchronized(invTileLock) {
match = invTilesIter.next(coord);
}
return match;
}
public void validateTile(int tx, int ty) {
synchronized(invTileLock) {
invTiles.setFlag(tx, ty, false);
pendingInvTiles.setFlag(tx, ty, false);
pendingInvTilesAlt.setFlag(tx, ty, false);
}
}
public boolean isInvalidTile(int tx, int ty) {
synchronized(invTileLock) {
return invTiles.getFlag(tx, ty);
}
}
public List<String> save() {
synchronized(invTileLock) {
invTiles.union(pendingInvTiles);
invTiles.union(pendingInvTilesAlt);
pendingInvTiles.clear();
pendingInvTilesAlt.clear();
return invTiles.save();
}
}
public void restore(List<String> saved) {
synchronized(invTileLock) {
TileFlags tf = new TileFlags();
tf.load(saved);
invTiles.union(tf);
}
}
public List<List<String>> saveZoomOut() {
ArrayList<List<String>> rslt = new ArrayList<List<String>>();
synchronized(invTileLock) {
boolean empty = true;
for (TileFlags tf : zoomOutInv) {
List<String> val;
if (tf == null) {
val = Collections.emptyList();
}
else {
val = tf.save();
if (val == null) {
val = Collections.emptyList();
}
else {
empty = false;
}
}
rslt.add(val);
}
for (TileFlags tf : zoomOutInvAccum) {
List<String> val;
if (tf == null) {
val = Collections.emptyList();
}
else {
val = tf.save();
if (val == null) {
val = Collections.emptyList();
}
else {
empty = false;
}
}
rslt.add(val);
}
if (empty) {
rslt = null;
}
}
return rslt;
}
public void restoreZoomOut(List<List<String>> dat) {
synchronized(invTileLock) {
int cnt = dat.size();
int cntaccum = 0;
if (cnt > zoomOutInv.size()) {
if (cnt == (2*zoomOutInv.size())) {
cntaccum = cnt / 2;
}
cnt = zoomOutInv.size();
}
for (int i = 0; i < cnt; i++) {
List<String> lst = dat.get(i);
TileFlags tf = null;
if ((lst != null) && (lst.size() > 0)) {
tf = new TileFlags();
tf.load(lst);
}
zoomOutInv.set(i, tf);
}
for (int i = 0; i < cntaccum; i++) {
List<String> lst = dat.get(i + cnt);
TileFlags tf = null;
if ((lst != null) && (lst.size() > 0)) {
tf = new TileFlags();
tf.load(lst);
}
zoomOutInvAccum.set(i, tf);
}
}
}
public int getInvCount() {
synchronized(invTileLock) {
return invTiles.countFlags();
}
}
public void clear() {
synchronized(invTileLock) {
invTiles.clear();
}
}
// Set to zoom out accum
public void setZoomOutInv(int x, int y, int zoomlevel) {
if (zoomlevel >= zoomOutLevels) {
return;
}
synchronized(invTileLock) {
TileFlags tf = zoomOutInvAccum.get(zoomlevel);
if (tf == null) {
tf = new TileFlags();
zoomOutInvAccum.set(zoomlevel, tf);
}
if ((((x >> zoomlevel) << zoomlevel) != x) ||
(((y >> zoomlevel) << zoomlevel) != y)) {
Log.info("setZoomOutInv(" + x + "," + y + "," + zoomlevel + ")");
}
tf.setFlag(x >> zoomlevel, y >> zoomlevel, true);
}
}
// Clear flag in active zoom out flags
public boolean clearZoomOutInv(int x, int y, int zoomlevel) {
if (zoomlevel >= zoomOutLevels) {
return false;
}
synchronized(invTileLock) {
TileFlags tf = zoomOutInv.get(zoomlevel);
if (tf == null) {
return false;
}
return tf.setFlag(x >> zoomlevel, y >> zoomlevel, false);
}
}
public static class ZoomOutCoord extends TileFlags.TileCoord {
public int zoomlevel;
}
// Start zoom out iteration (stash and reset accumulator)
public void startZoomOutIter() {
synchronized(invTileLock) {
ArrayList<TileFlags> tmplist = zoomOutInv;
zoomOutInv = zoomOutInvAccum;
for (int i = 0; i < tmplist.size(); i++) {
tmplist.set(i, null);
}
zoomOutInvAccum = tmplist;
zoomOutInvIter = null;
zoomOutInvIterLevel = 0;
}
}
public boolean nextZoomOutInv(ZoomOutCoord coord) {
synchronized(invTileLock) {
// Try existing iterator
if (zoomOutInvIter != null) {
if (zoomOutInvIter.hasNext()) {
zoomOutInvIter.next(coord);
coord.zoomlevel = zoomOutInvIterLevel;
coord.x = coord.x << zoomOutInvIterLevel;
coord.y = coord.y << zoomOutInvIterLevel;
return true;
}
zoomOutInvIter = null;
}
for (; zoomOutInvIterLevel < zoomOutInv.size(); zoomOutInvIterLevel++) {
TileFlags tf = zoomOutInv.get(zoomOutInvIterLevel);
if (tf != null) {
zoomOutInvIter = tf.getIterator();
if (zoomOutInvIter.hasNext()) {
zoomOutInvIter.next(coord);
coord.zoomlevel = zoomOutInvIterLevel;
coord.x = coord.x << zoomOutInvIterLevel;
coord.y = coord.y << zoomOutInvIterLevel;
return true;
}
else {
zoomOutInvIter = null;
}
}
}
}
return false;
}
}

View file

@ -0,0 +1,313 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.dynmap.common.DynmapListenerManager.EventType;
import org.dynmap.common.DynmapListenerManager.WorldEventListener;
import org.dynmap.common.DynmapListenerManager.PlayerEventListener;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.markers.AreaMarker;
import org.dynmap.markers.Marker;
import org.dynmap.markers.MarkerAPI;
import org.dynmap.markers.MarkerIcon;
import org.dynmap.markers.MarkerSet;
import org.dynmap.markers.impl.MarkerSignManager;
import org.dynmap.utils.Polygon;
/**
* Markers component - ties in the component system, both on the server and client
*/
public class MarkersComponent extends ClientComponent {
private MarkerAPI api;
private MarkerSignManager signmgr;
private MarkerIcon spawnicon;
private String spawnlbl;
private String worldborderlbl;
private MarkerSet offlineset;
private MarkerIcon offlineicon;
private MarkerSet spawnbedset;
private MarkerIcon spawnbedicon;
private String spawnbedformat;
private boolean removebedonplayerleave;
private long maxofflineage;
private boolean showSpawn;
private boolean showBorder;
private HashMap<String, Long> offline_times = new HashMap<String, Long>();
private static final String OFFLINE_PLAYERS_SETID = "offline_players";
private static final String PLAYER_SPAWN_BED_SETID = "spawn_beds";
public MarkersComponent(final DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
api = core.getMarkerAPI();
/* If configuration has enabled sign support, prime it too */
if(configuration.getBoolean("enablesigns", false)) {
signmgr = MarkerSignManager.initializeSignManager(core, configuration.getString("default-sign-set", MarkerSet.DEFAULT));
}
showBorder = configuration.getBoolean("showworldborder", false);
showSpawn = configuration.getBoolean("showspawn", false);
/* If we're posting spawn point markers, initialize and add world listener */
if(showSpawn) {
String ico = configuration.getString("spawnicon", MarkerIcon.WORLD);
spawnlbl = configuration.getString("spawnlabel", "Spawn");
spawnicon = api.getMarkerIcon(ico); /* Load it */
if(spawnicon == null) {
spawnicon = api.getMarkerIcon(MarkerIcon.WORLD);
}
}
if (showBorder) {
worldborderlbl = configuration.getString("worldborderlabel", "Border");
}
if (showSpawn || showBorder) {
/* Add listener for world loads */
WorldEventListener wel = new WorldEventListener() {
@Override
public void worldEvent(DynmapWorld w) {
DynmapLocation loc = w.getSpawnLocation(); /* Get location of spawn */
if(loc != null)
addUpdateWorld(w, loc);
}
};
core.listenerManager.addListener(EventType.WORLD_LOAD, wel);
/* Add listener for spawn changes */
core.listenerManager.addListener(EventType.WORLD_SPAWN_CHANGE, wel);
/* Initialize already loaded worlds */
for(DynmapWorld w : core.getMapManager().getWorlds()) {
DynmapLocation loc = w.getSpawnLocation();
if(loc != null)
addUpdateWorld(w, loc);
}
}
/* If showing offline players as markers */
if(configuration.getBoolean("showofflineplayers", false)) {
/* Make set, if needed */
offlineset = api.getMarkerSet(OFFLINE_PLAYERS_SETID);
if(offlineset == null) {
offlineset = api.createMarkerSet(OFFLINE_PLAYERS_SETID, configuration.getString("offlinelabel", "Offline"), null, true);
}
offlineset.setHideByDefault(configuration.getBoolean("offlinehidebydefault", true));
offlineset.setMinZoom(configuration.getInteger("offlineminzoom", 0));
maxofflineage = 60000L * configuration.getInteger("maxofflinetime", 30); /* 30 minutes */
/* Now, see if existing offline markers - check for last login on their users */
if(maxofflineage > 0) {
Set<Marker> prev_m = offlineset.getMarkers();
for(Marker m : prev_m) {
DynmapPlayer p = core.getServer().getOfflinePlayer(m.getMarkerID());
if(p != null) {
long ageout = p.getLastLoginTime() + maxofflineage;
if(ageout < System.currentTimeMillis()) {
m.deleteMarker();
}
else {
offline_times.put(p.getName(), ageout);
}
}
else {
m.deleteMarker();
}
}
}
offlineicon = api.getMarkerIcon(configuration.getString("offlineicon", "offlineuser"));
if(maxofflineage > 0) {
core.getServer().scheduleServerTask(new Runnable() {
public void run() {
long ts = System.currentTimeMillis();
ArrayList<String> deleted = new ArrayList<String>();
for(Map.Entry<String,Long> me : offline_times.entrySet()) {
if(ts > me.getValue()) {
deleted.add(me.getKey());
}
}
for(String id : deleted) {
Marker m = offlineset.findMarker(id);
if(m != null)
m.deleteMarker();
}
core.getServer().scheduleServerTask(this, 30 * 20);
}
}, 30 * 20); /* Check every 30 seconds */
}
/* Add listener for players coming and going */
core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
Marker m = offlineset.findMarker(p.getName());
if(m != null) {
m.deleteMarker();
offline_times.remove(p.getName());
}
}
});
core.listenerManager.addListener(EventType.PLAYER_QUIT, new PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
String pname = p.getName();
Marker m = offlineset.findMarker(pname);
if(m != null) {
m.deleteMarker();
offline_times.remove(p.getName());
}
if(core.playerList.isVisiblePlayer(pname)) {
DynmapLocation loc = p.getLocation();
m = offlineset.createMarker(p.getName(), core.getServer().stripChatColor(p.getDisplayName()), false,
loc.world, loc.x, loc.y, loc.z, offlineicon, true);
if(maxofflineage > 0)
offline_times.put(p.getName(), System.currentTimeMillis() + maxofflineage);
}
}
});
}
else {
/* Make set, if needed */
offlineset = api.getMarkerSet(OFFLINE_PLAYERS_SETID);
if(offlineset != null) {
offlineset.deleteMarkerSet();
}
}
/* If showing player spawn bed locations as markers */
if(configuration.getBoolean("showspawnbeds", false)) {
/* Make set, if needed */
spawnbedset = api.getMarkerSet(PLAYER_SPAWN_BED_SETID);
if(spawnbedset == null) {
spawnbedset = api.createMarkerSet(PLAYER_SPAWN_BED_SETID, configuration.getString("spawnbedlabel", "Spawn Beds"), null, true);
}
spawnbedset.setHideByDefault(configuration.getBoolean("spawnbedhidebydefault", true));
spawnbedset.setMinZoom(configuration.getInteger("spawnbedminzoom", 0));
spawnbedicon = api.getMarkerIcon(configuration.getString("spawnbedicon", "bed"));
spawnbedformat = configuration.getString("spawnbedformat", "%name%'s bed");
removebedonplayerleave = configuration.getBoolean("spawnbedremoveonplayerleave", true);
/* Add listener for players coming and going */
core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
updatePlayer(p);
}
});
if (removebedonplayerleave) {
core.listenerManager.addListener(EventType.PLAYER_QUIT, new PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
Marker m = spawnbedset.findMarker(p.getName() + "_bed");
if (m != null) {
m.deleteMarker();
}
}
});
}
core.listenerManager.addListener(EventType.PLAYER_BED_LEAVE, new PlayerEventListener() {
@Override
public void playerEvent(final DynmapPlayer p) {
core.getServer().scheduleServerTask(new Runnable() {
public void run() {
updatePlayer(p);
}
}, 0);
}
});
}
else {
/* Make set, if needed */
spawnbedset = api.getMarkerSet(PLAYER_SPAWN_BED_SETID);
if(spawnbedset != null) {
spawnbedset.deleteMarkerSet();
}
}
}
private void updatePlayer(DynmapPlayer p) {
DynmapLocation bl = p.getBedSpawnLocation();
Marker m = spawnbedset.findMarker(p.getName()+"_bed");
if(bl == null) { /* No bed location */
if(m != null) {
m.deleteMarker();
}
}
else {
if(m != null)
m.setLocation(bl.world, bl.x, bl.y, bl.z);
else
m = spawnbedset.createMarker(p.getName()+"_bed", spawnbedformat.replace("%name%", core.getServer().stripChatColor(p.getDisplayName())), false,
bl.world, bl.x, bl.y, bl.z,
spawnbedicon, true);
}
}
private void addUpdateWorld(DynmapWorld w, DynmapLocation loc) {
MarkerSet ms = api.getMarkerSet(MarkerSet.DEFAULT);
if(ms != null) {
String spawnid = "_spawn_" + w.getName();
Marker m = ms.findMarker(spawnid); /* See if defined */
if (showSpawn) {
if(m == null) { /* Not defined yet, add it */
ms.createMarker(spawnid, spawnlbl, w.getName(), loc.x, loc.y, loc.z,
spawnicon, false);
}
else {
m.setLocation(w.getName(), loc.x, loc.y, loc.z);
}
}
else {
if (m != null) {
m.deleteMarker();
}
}
String borderid = "_worldborder_" + w.getName();
AreaMarker am = ms.findAreaMarker(borderid);
Polygon p = null;
if (showBorder && w.showborder) {
p = w.getWorldBorder();
}
if ((p != null) && (p.size() > 1)) {
double[] x;
double[] z;
if (p.size() == 2) {
x = new double[4];
z = new double[4];
Polygon.Point2D p0 = p.getVertex(0);
Polygon.Point2D p1 = p.getVertex(1);
x[0] = p0.x; z[0] = p0.y;
x[1] = p0.x; z[1] = p1.y;
x[2] = p1.x; z[2] = p1.y;
x[3] = p1.x; z[3] = p0.y;
}
else {
int sz = p.size();
x = new double[sz];
z = new double[sz];
for (int i = 0; i < sz; i++) {
Polygon.Point2D pi = p.getVertex(i);
x[i] = pi.x; z[i] = pi.y;
}
}
if (am == null) {
am = ms.createAreaMarker(borderid, worldborderlbl, false, w.getName(), x, z, false);
}
else {
am.setCornerLocations(x, z);
}
am.setFillStyle(0.0, 0);
}
else {
if (am != null) {
am.deleteMarker();
}
}
}
}
@Override
public void dispose() {
if(signmgr != null) {
MarkerSignManager.terminateSignManager(this.core);
signmgr = null;
}
/* Don't unregister API - other plugins might be using it, and we want to keep non-persistent markers */
}
}

View file

@ -0,0 +1,311 @@
package org.dynmap;
import org.dynmap.MapType.ImageFormat;
import org.dynmap.common.DynmapListenerManager.EventType;
import org.dynmap.common.DynmapListenerManager.PlayerEventListener;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.debug.Debug;
import org.dynmap.storage.MapStorage;
import org.dynmap.utils.BufferOutputStream;
import org.dynmap.utils.DynmapBufferedImage;
import org.dynmap.utils.ImageIOManager;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.UUID;
/**
* Listen for player logins, and process player faces by fetching skins *
*/
public class PlayerFaces {
private boolean fetchskins;
private boolean refreshskins;
private String skinurl;
public MapStorage storage;
public enum FaceType {
FACE_8X8("8x8", 0),
FACE_16X16("16x16", 1),
FACE_32X32("32x32", 2),
BODY_32X32("body", 3);
public final String id;
public final int typeID;
FaceType(String id, int typeid) {
this.id = id;
this.typeID = typeid;
}
public static FaceType byID(String i_d) {
for (FaceType ft : values()) {
if (ft.id.equals(i_d)) {
return ft;
}
}
return null;
}
public static FaceType byTypeID(int tid) {
for (FaceType ft : values()) {
if (ft.typeID == tid) {
return ft;
}
}
return null;
}
}
private void copyLayersToTarget(BufferedImage srcimg, int layer1x, int layer1y, int layer2x, int layer2y, int w, int h, int[] dest, int destoff, int destscansize)
{
int[] l1 = new int[w * h];
int[] l2 = new int[w * h];
int imgh = srcimg.getHeight();
// Read layer 1
if (imgh >= (layer1y+h))
srcimg.getRGB(layer1x, layer1y, w, h, l1, 0, w);
// Read layer 2
if (imgh >= (layer2y+h))
srcimg.getRGB(layer2x, layer2y, w, h, l2, 0, w);
// Apply layer1 to layer 1
boolean transp = false;
int v = l2[0];
for (int i = 0; i < (w*h); i++) {
if ((l2[i] & 0xFF000000) == 0) {
transp = true;
break;
}
/* If any different values, render face too */
else if (l2[i] != v) {
transp = true;
break;
}
}
if(transp) {
for (int i = 0; i < (w*h); i++) {
if ((l2[i] & 0xFF000000) != 0)
l1[i] = l2[i];
}
}
// Write to dest
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
dest[destoff + (y*destscansize + x)] = l1[(y*w)+x];
}
}
}
private void copyLayersToTarget(BufferedImage srcimg, int layer1x, int layer1y, int layer2x, int layer2y, int w, int h, BufferedImage dest, int destoff, int destscansize)
{
int[] tmp = new int[w*h];
copyLayersToTarget(srcimg,layer1x,layer1y,layer2x,layer2y,w,h,tmp,0,w);
dest.setRGB(0,0,w,h,tmp,destoff,destscansize);
}
private class LoadPlayerImages implements Runnable {
private SkinUrlProvider mSkinUrlProvider;
public final String playername;
public final UUID playeruuid;
public final String playerskinurl;
public LoadPlayerImages(String playername, String playerskinurl, UUID playeruuid, SkinUrlProvider skinUrlProvider) {
this.playername = playername;
this.playeruuid = playeruuid;
this.playerskinurl = playerskinurl;
mSkinUrlProvider = skinUrlProvider;
}
public void run() {
boolean has_8x8 = storage.hasPlayerFaceImage(playername, FaceType.FACE_8X8);
boolean has_16x16 = storage.hasPlayerFaceImage(playername, FaceType.FACE_16X16);
boolean has_32x32 = storage.hasPlayerFaceImage(playername, FaceType.FACE_32X32);
boolean has_body = storage.hasPlayerFaceImage(playername, FaceType.BODY_32X32);
boolean missing_any = !(has_8x8 && has_16x16 && has_32x32 && has_body);
boolean is_64x32_skin = false;
BufferedImage img = null;
try {
if(fetchskins && (refreshskins || missing_any)) {
URL url = null;
if (mSkinUrlProvider == null) {
if (!skinurl.equals("")) {
url = new URL(skinurl.replace("%player%", URLEncoder.encode(playername, "UTF-8"))
.replace("%uuid%", playeruuid.toString()));
} else if (playerskinurl != null) {
url = new URL(playerskinurl);
}
} else {
url = mSkinUrlProvider.getSkinUrl(playername);
}
if (url != null)
img = ImageIO.read(url); /* Load skin for player */
}
} catch (IOException iox) {
Debug.debug("Error loading skin for '" + playername + "' - " + iox);
}
if(img == null) {
try {
InputStream in = getClass().getResourceAsStream("/char.png");
img = ImageIO.read(in); /* Load generic skin for player */
in.close();
} catch (IOException iox) {
Debug.debug("Error loading default skin for '" + playername + "' - " + iox);
}
}
if(img == null) { /* No image to process? Quit */
return;
}
if((img.getWidth() < 64) || (img.getHeight() < 32)) {
img.flush();
return;
}
else if( (img.getWidth() / img.getHeight()) == 2 ) { /* Is single layer skin? */
is_64x32_skin = true;
}
/* Get buffered image for face at original size */
int scale = img.getWidth()/8 /8;
BufferedImage faceOriginal = new BufferedImage(8*scale, 8*scale, BufferedImage.TYPE_INT_ARGB);
// Copy face and overlay to icon
copyLayersToTarget(img, 8*scale, 8*scale, 40*scale, 8*scale, 8*scale, 8*scale, faceOriginal, 0, 8*scale);
/* Get buffered image for face at 8x8 */
DynmapBufferedImage face8x8 = DynmapBufferedImage.allocateBufferedImage(8, 8);
Image face8x8_image = faceOriginal.getScaledInstance(8,8,BufferedImage.SCALE_SMOOTH);
BufferedImage face8x8_buff = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
face8x8_buff.getGraphics().drawImage(face8x8_image,0,0,null);
face8x8_buff.getRGB(0,0,8,8,face8x8.argb_buf,0,8);
/* Write 8x8 file */
if(refreshskins || (!has_8x8)) {
BufferOutputStream bos = ImageIOManager.imageIOEncode(face8x8.buf_img, ImageFormat.FORMAT_PNG);
if (bos != null) {
storage.setPlayerFaceImage(playername, FaceType.FACE_8X8, bos);
}
}
/* Write 16x16 file */
if(refreshskins || (!has_16x16)) {
/* Make 16x16 version */
DynmapBufferedImage face16x16 = DynmapBufferedImage.allocateBufferedImage(16, 16);
Image face16x16_image = faceOriginal.getScaledInstance(16,16,BufferedImage.SCALE_SMOOTH);
BufferedImage face16x16_buff = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
face16x16_buff.getGraphics().drawImage(face16x16_image,0,0,null);
face16x16_buff.getRGB(0,0,16,16,face16x16.argb_buf,0,16);
BufferOutputStream bos = ImageIOManager.imageIOEncode(face16x16.buf_img, ImageFormat.FORMAT_PNG);
if (bos != null) {
storage.setPlayerFaceImage(playername, FaceType.FACE_16X16, bos);
}
DynmapBufferedImage.freeBufferedImage(face16x16);
face16x16_buff.flush();
}
/* Write 32x32 file */
if(refreshskins || (!has_32x32)) {
/* Make 32x32 version */
DynmapBufferedImage face32x32 = DynmapBufferedImage.allocateBufferedImage(32, 32);
Image face32x32_image = faceOriginal.getScaledInstance(32,32,BufferedImage.SCALE_SMOOTH);
BufferedImage face32x32_buff = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
face32x32_buff.getGraphics().drawImage(face32x32_image,0,0,null);
face32x32_buff.getRGB(0,0,32,32,face32x32.argb_buf,0,32);
BufferOutputStream bos = ImageIOManager.imageIOEncode(face32x32.buf_img, ImageFormat.FORMAT_PNG);
if (bos != null) {
storage.setPlayerFaceImage(playername, FaceType.FACE_32X32, bos);
}
DynmapBufferedImage.freeBufferedImage(face32x32);
face32x32_buff.flush();
}
/* Write body file */
if(refreshskins || (!has_body)) {
Image skin_image = null;
BufferedImage skin_buff = null;
if (is_64x32_skin){
skin_image = img.getScaledInstance(64,32,BufferedImage.SCALE_SMOOTH);
skin_buff = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
skin_buff.getGraphics().drawImage(skin_image,0,0,null);
} else {
skin_image = img.getScaledInstance(64,64,BufferedImage.SCALE_SMOOTH);
skin_buff = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
skin_buff.getGraphics().drawImage(skin_image,0,0,null);
}
/* Make 32x32 version */
DynmapBufferedImage body32x32 = DynmapBufferedImage.allocateBufferedImage(32, 32);
/* Copy face at 12,0 to 20,8 (already handled accessory) */
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
body32x32.argb_buf[i*32+j+12] = face8x8.argb_buf[i*8 + j];
}
}
/* Copy body at 20,20 and chest at 20,36 to 8,12 */
copyLayersToTarget(skin_buff, 20, 20, 20, 36, 8, 12, body32x32.argb_buf, 8*32+12, 32);
/* Copy right leg at 4,20 and 4,36 to 20,12 */
copyLayersToTarget(skin_buff, 4, 20, 4, 36, 4, 12, body32x32.argb_buf, 20*32+12, 32);
/* Copy left leg at 4,20 if old format or 20,52 and 4,53 to 20,16 */
if(is_64x32_skin) {
skin_buff.getRGB(4, 20, 4, 12, body32x32.argb_buf, 20*32+16, 32);
}
else {
copyLayersToTarget(skin_buff, 20, 52, 4, 52, 4, 12, body32x32.argb_buf, 20 * 32 + 16, 32);
}
/* Copy right arm at 44,20 and 44,36 to 8,8 */
copyLayersToTarget(skin_buff, 44, 20, 44, 36, 4, 12, body32x32.argb_buf, 8*32+8, 32);
/* Copy left arm at 44,20 if old format or 36,52 and 52,52 to 8,20 */
if(is_64x32_skin) {
skin_buff.getRGB(44, 20, 4, 12, body32x32.argb_buf, 8*32+20, 32);
}
else {
copyLayersToTarget(skin_buff, 36, 52, 52, 52, 4, 12, body32x32.argb_buf, 8 * 32 + 20, 32);
}
BufferOutputStream bos = ImageIOManager.imageIOEncode(body32x32.buf_img, ImageFormat.FORMAT_PNG);
if (bos != null) {
storage.setPlayerFaceImage(playername, FaceType.BODY_32X32, bos);
}
DynmapBufferedImage.freeBufferedImage(body32x32);
skin_buff.flush();
}
DynmapBufferedImage.freeBufferedImage(face8x8);
face8x8_buff.flush();
faceOriginal.flush();
img.flush();
}
}
public PlayerFaces(DynmapCore core) {
fetchskins = core.configuration.getBoolean("fetchskins", true); /* Control whether to fetch skins */
refreshskins = core.configuration.getBoolean("refreshskins", true); /* Control whether to update existing fetched skins or faces */
skinurl = core.configuration.getString("skin-url", "");
// These don't work anymore - Mojang retired them
if (skinurl.equals("http://s3.amazonaws.com/MinecraftSkins/%player%.png") ||
skinurl.equals("http://skins.minecraft.net/MinecraftSkins/%player%.png")) {
skinurl = "";
}
core.listenerManager.addListener(EventType.PLAYER_JOIN, new PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
Runnable job = new LoadPlayerImages(p.getName(), p.getSkinURL(), p.getUUID(), core.skinUrlProvider);
MapManager.scheduleDelayedJob(job, 0);
}
});
storage = core.getDefaultMapStorage();
}
}

View file

@ -0,0 +1,193 @@
package org.dynmap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.common.DynmapServerInterface;
public class PlayerList {
private DynmapServerInterface server;
private HashSet<String> hiddenPlayerNames = new HashSet<String>();
private File hiddenPlayersFile;
private ConfigurationNode configuration;
private DynmapPlayer[] online;
private HashMap<String, Set<String>> invisibility_asserts = new HashMap<String, Set<String>>();
private HashMap<String, Set<String>> visibility_asserts = new HashMap<String, Set<String>>();
public PlayerList(DynmapServerInterface server, File hiddenPlayersFile, ConfigurationNode configuration) {
this.server = server;
this.hiddenPlayersFile = hiddenPlayersFile;
this.configuration = configuration;
updateOnlinePlayers(null);
}
public void save() {
OutputStream stream;
try {
stream = new FileOutputStream(hiddenPlayersFile);
OutputStreamWriter writer = new OutputStreamWriter(stream);
for (String player : hiddenPlayerNames) {
writer.write(player);
writer.write("\n");
}
writer.close();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void load() {
try {
Scanner scanner = new Scanner(hiddenPlayersFile);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
hiddenPlayerNames.add(line);
}
scanner.close();
} catch (FileNotFoundException e) {
return;
}
}
public void hide(String playerName) {
hiddenPlayerNames.add(playerName.toLowerCase());
save();
}
public void show(String playerName) {
hiddenPlayerNames.remove(playerName.toLowerCase());
save();
}
public void setVisible(String playerName, boolean visible) {
if (visible ^ configuration.getBoolean("display-whitelist", false))
show(playerName);
else
hide(playerName);
}
public void assertVisiblilty(String playerName, boolean visible, String plugin_id) {
playerName = playerName.toLowerCase();
if(visible) {
Set<String> ids = visibility_asserts.get(playerName);
if(ids == null) {
ids = new HashSet<String>();
visibility_asserts.put(playerName, ids);
}
ids.add(plugin_id);
}
else {
Set<String> ids = visibility_asserts.get(playerName);
if(ids != null) {
ids.remove(plugin_id);
if(ids.isEmpty()) {
visibility_asserts.remove(playerName);
}
}
}
}
public void assertInvisiblilty(String playerName, boolean invisible, String plugin_id) {
playerName = playerName.toLowerCase();
if(invisible) {
Set<String> ids = invisibility_asserts.get(playerName);
if(ids == null) {
ids = new HashSet<String>();
invisibility_asserts.put(playerName, ids);
}
ids.add(plugin_id);
}
else {
Set<String> ids = invisibility_asserts.get(playerName);
if(ids != null) {
ids.remove(plugin_id);
if(ids.isEmpty()) {
invisibility_asserts.remove(playerName);
}
}
}
}
public DynmapPlayer[] getOnlinePlayers() {
return Arrays.copyOf(online, online.length);
}
public List<DynmapPlayer> getVisiblePlayers(String worldName) {
ArrayList<DynmapPlayer> visiblePlayers = new ArrayList<DynmapPlayer>();
DynmapPlayer[] onlinePlayers = online; /* Use copied list - we don't call from server thread */
boolean useWhitelist = configuration.getBoolean("display-whitelist", false);
for (int i = 0; i < onlinePlayers.length; i++) {
DynmapPlayer p = onlinePlayers[i];
if(p == null) continue;
if((worldName != null) && (p.getWorld().equals(worldName) == false)) continue;
String pname = p.getName().toLowerCase();
if (!(useWhitelist ^ hiddenPlayerNames.contains(pname))) {
if(!invisibility_asserts.containsKey(pname)) {
visiblePlayers.add(p);
}
}
else if(visibility_asserts.containsKey(pname)) {
visiblePlayers.add(p);
}
}
return visiblePlayers;
}
public List<DynmapPlayer> getVisiblePlayers() {
return getVisiblePlayers(null);
}
public List<DynmapPlayer> getHiddenPlayers() {
ArrayList<DynmapPlayer> hidden = new ArrayList<DynmapPlayer>();
DynmapPlayer[] onlinePlayers = online; /* Use copied list - we don't call from server thread */
boolean useWhitelist = configuration.getBoolean("display-whitelist", false);
for (int i = 0; i < onlinePlayers.length; i++) {
DynmapPlayer p = onlinePlayers[i];
if(p == null) continue;
String pname = p.getName().toLowerCase();
if (!(useWhitelist ^ hiddenPlayerNames.contains(pname))) {
if(invisibility_asserts.containsKey(pname)) {
hidden.add(p);
}
}
else if(!visibility_asserts.containsKey(pname)) {
hidden.add(p);
}
}
return hidden;
}
public boolean isVisiblePlayer(String p) {
p = p.toLowerCase();
boolean useWhitelist = configuration.getBoolean("display-whitelist", false);
return (!(useWhitelist ^ hiddenPlayerNames.contains(p))) && (!invisibility_asserts.containsKey(p));
}
/**
* Call this from server thread to update player list safely
*/
void updateOnlinePlayers(String skipone) {
DynmapPlayer[] players = server.getOnlinePlayers();
DynmapPlayer[] pl = new DynmapPlayer[players.length];
System.arraycopy(players, 0, pl, 0, pl.length);
if(skipone != null) {
for(int i = 0; i < pl.length; i++)
if(pl[i].getName().equals(skipone))
pl[i] = null;
}
online = pl;
}
}

View file

@ -0,0 +1,73 @@
package org.dynmap;
import static org.dynmap.JSONUtils.s;
import org.dynmap.common.DynmapListenerManager;
import org.dynmap.common.DynmapListenerManager.ChatEventListener;
import org.dynmap.common.DynmapListenerManager.EventType;
import org.dynmap.common.DynmapPlayer;
import org.json.simple.JSONObject;
public class SimpleWebChatComponent extends Component {
public SimpleWebChatComponent(final DynmapCore plugin, final ConfigurationNode configuration) {
super(plugin, configuration);
plugin.events.addListener("webchat", new Event.Listener<ChatEvent>() {
@Override
public void triggered(ChatEvent t) {
if(plugin.getServer().sendWebChatEvent(t.source, t.name, t.message)) {
String msg;
String msgfmt = plugin.configuration.getString("webmsgformat", null);
if(msgfmt != null) {
msgfmt = unescapeString(msgfmt);
msg = msgfmt.replace("%playername%", t.name).replace("%message%", t.message);
}
else {
msg = unescapeString(plugin.configuration.getString("webprefix", "\u00A72[WEB] ")) + t.name + ": " + unescapeString(plugin.configuration.getString("websuffix", "\u00A7f")) + t.message;
}
plugin.getServer().broadcastMessage(msg);
if (core.mapManager != null) {
core.mapManager.pushUpdate(new Client.ChatMessage("web", null, t.name, t.message, null));
}
}
}
});
plugin.events.addListener("buildclientconfiguration", new Event.Listener<JSONObject>() {
@Override
public void triggered(JSONObject t) {
s(t, "allowchat", configuration.getBoolean("allowchat", false));
}
});
if (configuration.getBoolean("allowchat", false)) {
plugin.listenerManager.addListener(EventType.PLAYER_CHAT, new ChatEventListener() {
@Override
public void chatEvent(DynmapPlayer p, String msg) {
if(core.disable_chat_to_web) return;
msg = core.scanAndReplaceLog4JMacro(msg);
if(core.mapManager != null)
core.mapManager.pushUpdate(new Client.ChatMessage("player", "", p.getDisplayName(), msg, p.getName()));
}
});
plugin.listenerManager.addListener(EventType.PLAYER_JOIN, new DynmapListenerManager.PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
if(core.disable_chat_to_web) return;
if((core.mapManager != null) && (core.playerList != null) && (core.playerList.isVisiblePlayer(p.getName()))) {
core.mapManager.pushUpdate(new Client.PlayerJoinMessage(p.getDisplayName(), p.getName()));
}
}
});
plugin.listenerManager.addListener(EventType.PLAYER_QUIT, new DynmapListenerManager.PlayerEventListener() {
@Override
public void playerEvent(DynmapPlayer p) {
if(core.disable_chat_to_web) return;
if((core.mapManager != null) && (core.playerList != null) && (core.playerList.isVisiblePlayer(p.getName()))) {
core.mapManager.pushUpdate(new Client.PlayerQuitMessage(p.getDisplayName(), p.getName()));
}
}
});
}
}
}

View file

@ -0,0 +1,7 @@
package org.dynmap;
import java.net.URL;
public interface SkinUrlProvider {
URL getSkinUrl(String playerName);
}

View file

@ -0,0 +1,10 @@
package org.dynmap;
public class TestComponent extends Component {
public TestComponent(DynmapCore plugin, ConfigurationNode configuration) {
super(plugin, configuration);
Log.info("Hello! I'm a component that does stuff! Like saying what is in my configuration: " + configuration.getString("stuff"));
}
}

View file

@ -0,0 +1,121 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.HashMap;
public class UpdateQueue {
public Object lock = new Object();
private HashMap<UpdateRec,UpdateRec> updateSet = new HashMap<UpdateRec,UpdateRec>();
private UpdateRec orderedlist = null; /* Oldest to youngest */
private static final long maxUpdateAge = 120000;
private static final long ageOutPeriod = 5000;
private long lastageout = 0;
private static class UpdateRec {
Client.Update u;
UpdateRec next;
UpdateRec prev;
@Override
public boolean equals(Object o) {
if(o instanceof UpdateRec)
return u.equals(((UpdateRec)o).u);
return false;
}
@Override
public int hashCode() {
return u.hashCode();
}
}
private void doAgeOut(long now) {
/* If we're due */
if((now < lastageout) || (now > (lastageout + ageOutPeriod))) {
lastageout = now;
long deadline = now - maxUpdateAge;
while((orderedlist != null) && (orderedlist.u.timestamp < deadline)) {
UpdateRec r = orderedlist;
updateSet.remove(r); /* Remove record from set */
if(r.next == r) {
orderedlist = null;
}
else {
orderedlist = r.next;
r.next.prev = r.prev;
r.prev.next = r.next;
}
r.next = r.prev = null;
}
}
}
public void pushUpdate(Client.Update obj) {
synchronized (lock) {
/* Do inside lock - prevent delay between time and actual work */
long now = System.currentTimeMillis();
doAgeOut(now); /* Consider age out */
UpdateRec r = new UpdateRec();
r.u = obj;
r.u.timestamp = now; // Use our timestamp: makes sure order is preserved
UpdateRec oldr = updateSet.remove(r); /* Try to remove redundant event */
if(oldr != null) { /* If found, remove from ordered list too */
if(oldr.next == oldr) { /* Only one? */
orderedlist = null;
}
else {
if(orderedlist == oldr) { /* We're oldest? */
orderedlist = oldr.next;
}
oldr.next.prev = oldr.prev;
oldr.prev.next = oldr.next;
}
oldr.next = oldr.prev = null;
}
updateSet.put(r, r);
/* Add to end of ordered list */
if(orderedlist == null) {
orderedlist = r;
r.next = r.prev = r;
}
else {
r.next = orderedlist;
r.prev = orderedlist.prev;
r.next.prev = r.prev.next = r;
}
}
}
private ArrayList<Client.Update> tmpupdates = new ArrayList<Client.Update>();
public Client.Update[] getUpdatedObjects(long since) {
Client.Update[] updates;
synchronized (lock) {
long now = System.currentTimeMillis();
doAgeOut(now); /* Consider age out */
tmpupdates.clear();
if(orderedlist != null) {
UpdateRec r = orderedlist.prev; /* Get newest */
while(r != null) {
if(r.u.timestamp >= since) {
tmpupdates.add(r.u);
if(r == orderedlist)
r = null;
else
r = r.prev;
}
else {
r = null;
}
}
}
// Reverse output.
updates = new Client.Update[tmpupdates.size()];
for (int i = 0; i < updates.length; i++) {
updates[i] = tmpupdates.get(updates.length-1-i);
}
}
return updates;
}
}

View file

@ -0,0 +1,360 @@
package org.dynmap;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.servlet.LoginServlet;
public class WebAuthManager {
private HashMap<String, String> pwdhash_by_userid = new HashMap<String, String>();
private HashMap<String, String> pending_registrations = new HashMap<String, String>();
private String hashsalt;
private File pfile;
public static final String WEBAUTHFILE = "webauth.txt";
private static final String HASHSALT = "$HASH_SALT$";
private static final String PWDHASH_PREFIX = "hash.";
private SecureRandom rnd = new SecureRandom();
private DynmapCore core;
private String publicRegistrationURL;
public WebAuthManager(DynmapCore core) {
this.core = core;
pfile = new File(core.getDataFolder(), WEBAUTHFILE);
if(pfile.canRead()) {
FileReader rf = null;
try {
rf = new FileReader(pfile);
Properties p = new Properties();
p.load(rf);
hashsalt = p.getProperty(HASHSALT);
for(String k : p.stringPropertyNames()) {
if(k.equals(HASHSALT)) {
hashsalt = p.getProperty(k);
}
else if(k.startsWith(PWDHASH_PREFIX)) { /* Load password hashes */
pwdhash_by_userid.put(k.substring(PWDHASH_PREFIX.length()).toLowerCase(), p.getProperty(k));
}
}
} catch (IOException iox) {
Log.severe("Cannot read " + WEBAUTHFILE);
} finally {
if(rf != null) { try { rf.close(); } catch (IOException iox) {} }
}
}
if(hashsalt == null) { /* No hashsalt */
hashsalt = Long.toHexString(rnd.nextLong());
}
}
public boolean save() {
boolean success = false;
FileWriter fw = null;
try {
fw = new FileWriter(pfile);
Properties p = new Properties();
p.setProperty(HASHSALT, hashsalt); /* Save salt */
for(String k : pwdhash_by_userid.keySet()) {
p.setProperty(PWDHASH_PREFIX + k, pwdhash_by_userid.get(k));
}
p.store(fw, "DO NOT EDIT THIS FILE");
success = true;
} catch (IOException iox) {
Log.severe("Error writing " + WEBAUTHFILE);
} finally {
if(fw != null) { try { fw.close(); } catch (IOException iox) {} }
}
if(success)
core.events.trigger("loginupdated", null);
return success;
}
private String makeHash(String pwd) {
String check = hashsalt + pwd;
try {
byte[] checkbytes = check.getBytes("UTF-8");
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] rslt = md.digest(checkbytes);
String rslthash = "";
for(int i = 0; i < rslt.length; i++) {
rslthash += String.format("%02X", 0xFF & (int)rslt[i]);
}
return rslthash;
} catch (NoSuchAlgorithmException nsax) {
} catch (UnsupportedEncodingException uex) {
}
return null;
}
public boolean checkLogin(String uid, String pwd) {
uid = uid.toLowerCase();
if(uid.equals(LoginServlet.USERID_GUEST)) {
return true;
}
String hash = pwdhash_by_userid.get(uid);
if(hash == null) {
return false;
}
if(core.getServer().isPlayerBanned(uid)) {
return false;
}
String checkhash = makeHash(pwd);
return hash.equals(checkhash);
}
public boolean registerLogin(String uid, String pwd, String passcode) {
uid = uid.toLowerCase();
if(uid.equals(LoginServlet.USERID_GUEST)) {
return false;
}
if(core.getServer().isPlayerBanned(uid)) {
return false;
}
passcode = passcode.toLowerCase();
String kcode = pending_registrations.remove(uid);
if(kcode == null) {
return false;
}
if(!kcode.equals(passcode)) {
return false;
}
String hash = makeHash(pwd);
pwdhash_by_userid.put(uid, hash);
return save();
}
public boolean unregisterLogin(String uid) {
if(uid.equals(LoginServlet.USERID_GUEST)) {
return true;
}
uid = uid.toLowerCase();
pwdhash_by_userid.remove(uid);
return save();
}
public boolean isRegistered(String uid) {
if(uid.equals(LoginServlet.USERID_GUEST)) {
return false;
}
uid = uid.toLowerCase();
return pwdhash_by_userid.containsKey(uid);
}
boolean processCompletedRegister(String uid, String pc, String hash) {
uid = uid.toLowerCase();
if(uid.equals(LoginServlet.USERID_GUEST)) {
return false;
}
if(core.getServer().isPlayerBanned(uid)) {
return false;
}
String kcode = pending_registrations.remove(uid);
if(kcode == null) {
return false;
}
pc = pc.toLowerCase();
if(!kcode.equals(pc)) {
return false;
}
pwdhash_by_userid.put(uid, hash);
return save();
}
public static final boolean checkUserName(String name) {
int nlen = name.length();
if ((nlen > 0) && (nlen <= 16)) {
for (int i = 0; i < nlen; i++) {
if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".indexOf(name.charAt(i)) < 0) {
return false;
}
}
return true;
}
return false;
}
public boolean processWebRegisterCommand(DynmapCore core, DynmapCommandSender sender, DynmapPlayer player, String[] args) {
String uid = null;
boolean other = false;
if(args.length > 1) {
if(!core.checkPlayerPermission(sender, "webregister.other")) {
sender.sendMessage("You're not authorised to access web registration info for other players");
return true;
}
uid = args[1];
other = true;
}
else if (player == null) { /* Console? */
sender.sendMessage("Must provide username to access web registration info");
return true;
}
else {
uid = player.getName();
}
if (checkUserName(uid) == false) {
sender.sendMessage("Invalid username. Did you type it correctly?");
return true;
}
String regkey = String.format("%04d-%04d", rnd.nextInt(10000), rnd.nextInt(10000));
pending_registrations.put(uid.toLowerCase(), regkey.toLowerCase());
sender.sendMessage("Registration pending for username: " + uid);
sender.sendMessage("Registration code: " + regkey);
publicRegistrationURL = core.configuration.getString("publicURL", "index.html");
sender.sendMessage("Enter username and registration code when prompted on web page (" + publicRegistrationURL.toString() + ") to complete registration");
if(other) {
DynmapPlayer p = core.getServer().getPlayer(uid);
if(p != null && sender != p) {
p.sendMessage("The registration of your account for web access has been started.");
p.sendMessage("To complete the process, access the Login page on the Dynmap map");
p.sendMessage("Registration code: " + regkey);
p.sendMessage("Enter your username and registration code when prompted on web page (" + publicRegistrationURL.toString() + ") to complete registration");
}
}
core.events.trigger("loginupdated", null);
return true;
}
String getLoginPHP(boolean wrap) {
StringBuilder sb = new StringBuilder();
if (wrap) {
sb.append("<?php\n");
}
sb.append("$pwdsalt = '").append(hashsalt).append("';\n");
/* Create password hash */
sb.append("$pwdhash = array(\n");
for(String uid : pwdhash_by_userid.keySet()) {
sb.append(" \'").append(esc(uid)).append("\' => \'").append(esc(pwdhash_by_userid.get(uid))).append("\',\n");
}
sb.append(");\n");
/* Create registration table */
sb.append("$pendingreg = array(\n");
for(String uid : pending_registrations.keySet()) {
sb.append(" \'").append(esc(uid)).append("\' => \'").append(esc(pending_registrations.get(uid))).append("\',\n");
}
sb.append(");\n");
if (wrap) {
sb.append("?>\n");
}
return sb.toString();
}
public static String esc(String s) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(c == '\\')
sb.append("\\\\");
else if(c == '\'')
sb.append("\\\'");
else
sb.append(c);
}
return sb.toString();
}
String getAccessPHP(boolean wrap) {
StringBuilder sb = new StringBuilder();
if (wrap) {
sb.append("<?php\n");
}
ArrayList<String> mid = new ArrayList<String>();
/* Create world access list */
sb.append("$worldaccess = array(\n");
for(DynmapWorld w : core.getMapManager().getWorlds()) {
if(w.isProtected()) {
String perm = "world." + w.getName();
sb.append(" \'").append(esc(w.getName())).append("\' => \'");
for(String uid : pwdhash_by_userid.keySet()) {
if(core.getServer().checkPlayerPermission(uid, perm)) {
sb.append("[").append(esc(uid)).append("]");
}
}
sb.append("\',\n");
}
for(MapType mt : w.maps) {
if(mt.isProtected()) {
mid.add(w.getName() + "." + mt.getPrefix());
}
}
}
sb.append(");\n");
/* Create map access list */
sb.append("$mapaccess = array(\n");
for(String id : mid) {
String perm = "map." + id;
sb.append(" \'").append(esc(id)).append("\' => \'");
for(String uid : pwdhash_by_userid.keySet()) {
if(core.getServer().checkPlayerPermission(uid, perm)) {
sb.append("[").append(esc(uid)).append("]");
}
}
sb.append("\',\n");
}
sb.append(");\n");
HashSet<String> cantseeall = new HashSet<String>();
String perm = "playermarkers.seeall";
sb.append("$seeallmarkers = \'");
for(String uid : pwdhash_by_userid.keySet()) {
if(core.getServer().checkPlayerPermission(uid, perm)) {
sb.append("[").append(esc(uid)).append("]");
}
else {
cantseeall.add(uid);
}
}
sb.append("\';\n");
/* Add visibility lists for each player that doesn't see everything */
sb.append("$playervisible = array(\n");
for(String id : cantseeall) {
id = id.toLowerCase();
Set<String> vis = core.getPlayersVisibleToPlayer(id);
if((vis.size() == 1) && vis.contains(id)) continue;
sb.append(" \'").append(esc(id)).append("\' => \'");
for(String uid : vis) {
sb.append("[").append(esc(uid)).append("]");
}
sb.append("\',\n");
}
sb.append(");\n");
core.getDefaultMapStorage().addPaths(sb, core);
if (wrap) {
sb.append("?>\n");
}
return sb.toString();
}
static String getDisabledAccessPHP(DynmapCore core, boolean wrap) {
StringBuilder sb = new StringBuilder();
if (wrap) {
sb.append("<?php\n");
}
core.getDefaultMapStorage().addPaths(sb, core);
if (wrap) {
sb.append("?>\n");
}
return sb.toString();
}
boolean pendingRegisters() {
return (pending_registrations.size() > 0);
}
Set<String> getUserIDs() {
HashSet<String> lst = new HashSet<String>();
lst.addAll(pwdhash_by_userid.keySet());
lst.addAll(pending_registrations.keySet());
return lst;
}
}

View file

@ -0,0 +1,322 @@
package org.dynmap.common;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import org.dynmap.hdmap.HDBlockModels;
/* Generic biome mapping */
public class BiomeMap {
public static final int NO_INDEX = -2;
private static BiomeMap[] biome_by_index = new BiomeMap[256];
private static Map<String, BiomeMap> biome_by_rl = new HashMap<String, BiomeMap>(256);
// Tracks registered IDs for O(1) uniqueness checks during initialization
private static final HashSet<String> biome_ids = new HashSet<String>(256);
public static final BiomeMap NULL = new BiomeMap(-1, "NULL", 0.5, 0.5, 0xFFFFFF, 0, 0, null);
public static final BiomeMap OCEAN = new BiomeMap(0, "OCEAN", "minecraft:ocean");
public static final BiomeMap PLAINS = new BiomeMap(1, "PLAINS", 0.8, 0.4, "minecraft:plains");
public static final BiomeMap DESERT = new BiomeMap(2, "DESERT", 2.0, 0.0, "minecraft:desert");
public static final BiomeMap EXTREME_HILLS = new BiomeMap(3, "EXTREME_HILLS", 0.2, 0.3, "minecraft:mountains");
public static final BiomeMap FOREST = new BiomeMap(4, "FOREST", 0.7, 0.8, "minecraft:forest");
public static final BiomeMap TAIGA = new BiomeMap(5, "TAIGA", 0.05, 0.8, "minecraft:taiga");
public static final BiomeMap SWAMPLAND = new BiomeMap(6, "SWAMPLAND", 0.8, 0.9, 0xE0FFAE, 0x2e282a, 0x902c52, "minecraft:swamp");
public static final BiomeMap RIVER = new BiomeMap(7, "RIVER", "minecraft:river");
public static final BiomeMap HELL = new BiomeMap(8, "HELL", 2.0, 0.0, "minecraft:nether");
public static final BiomeMap SKY = new BiomeMap(9, "SKY", "minecraft:the_end");
public static final BiomeMap FROZEN_OCEAN = new BiomeMap(10, "FROZEN_OCEAN", 0.0, 0.5, "minecraft:frozen_ocean");
public static final BiomeMap FROZEN_RIVER = new BiomeMap(11, "FROZEN_RIVER", 0.0, 0.5, "minecraft:frozen_river");
public static final BiomeMap ICE_PLAINS = new BiomeMap(12, "ICE_PLAINS", 0.0, 0.5, "minecraft:snowy_tundra");
public static final BiomeMap ICE_MOUNTAINS = new BiomeMap(13, "ICE_MOUNTAINS", 0.0, 0.5, "minecraft:snowy_mountains");
public static final BiomeMap MUSHROOM_ISLAND = new BiomeMap(14, "MUSHROOM_ISLAND", 0.9, 1.0, "minecraft:mushroom_fields");
public static final BiomeMap MUSHROOM_SHORE = new BiomeMap(15, "MUSHROOM_SHORE", 0.9, 1.0, "minecraft:mushroom_field_shore");
public static final BiomeMap BEACH = new BiomeMap(16, "BEACH", 0.8, 0.4, "minecraft:beach");
public static final BiomeMap DESERT_HILLS = new BiomeMap(17, "DESERT_HILLS", 2.0, 0.0, "minecraft:desert_hills");
public static final BiomeMap FOREST_HILLS = new BiomeMap(18, "FOREST_HILLS", 0.7, 0.8, "minecraft:wooded_hills");
public static final BiomeMap TAIGA_HILLS = new BiomeMap(19, "TAIGA_HILLS", 0.05, 0.8, "minecraft:taiga_hills");
public static final BiomeMap SMALL_MOUNTAINS = new BiomeMap(20, "SMALL_MOUNTAINS", 0.2, 0.8, "minecraft:mountain_edge");
public static final BiomeMap JUNGLE = new BiomeMap(21, "JUNGLE", 1.2, 0.9, "minecraft:jungle");
public static final BiomeMap JUNGLE_HILLS = new BiomeMap(22, "JUNGLE_HILLS", 1.2, 0.9, "minecraft:jungle_hills");
public static final int LAST_WELL_KNOWN = 175;
private double tmp;
private double rain;
private int watercolormult;
private int grassmult;
private int foliagemult;
private Optional<?> biomeObj = Optional.empty();
private final String id;
private final String resourcelocation;
private final int index;
private int biomeindex256; // Standard biome mapping index (for 256 x 256)
private boolean isDef;
private static boolean loadDone = false;
public static void loadWellKnownByVersion(String mcver) {
if (loadDone) return;
if (HDBlockModels.checkVersionRange(mcver, "1.7.0-")) {
new BiomeMap(23, "JUNGLE_EDGE", 0.95, 0.8, "minecraft:jungle_edge");
new BiomeMap(24, "DEEP_OCEAN", "minecraft:deep_ocean");
new BiomeMap(25, "STONE_BEACH", 0.2, 0.3, "minecraft:stone_shore");
new BiomeMap(26, "COLD_BEACH", 0.05, 0.3, "minecraft:snowy_beach");
new BiomeMap(27, "BIRCH_FOREST", 0.6, 0.6, "minecraft:birch_forest");
new BiomeMap(28, "BIRCH_FOREST_HILLS", 0.6, 0.6, "minecraft:birch_forest_hills");
new BiomeMap(29, "ROOFED_FOREST", 0.7, 0.8, 0xFFFFFF, 0x28340A, 0, "minecraft:dark_forest");
new BiomeMap(30, "COLD_TAIGA", -0.5, 0.4, "minecraft:snowy_taiga");
new BiomeMap(31, "COLD_TAIGA_HILLS", -0.5, 0.4, "minecraft:snowy_taiga_hills");
new BiomeMap(32, "MEGA_TAIGA", 0.3, 0.8, "minecraft:giant_tree_taiga");
new BiomeMap(33, "MEGA_TAIGA_HILLS", 0.3, 0.8, "minecraft:giant_tree_taiga_hills");
new BiomeMap(34, "EXTREME_HILLS_PLUS", 0.2, 0.3, "minecraft:wooded_mountains");
new BiomeMap(35, "SAVANNA", 1.2, 0.0, "minecraft:savanna");
new BiomeMap(36, "SAVANNA_PLATEAU", 1.0, 0.0, "minecraft:savanna_plateau");
new BiomeMap(37, "MESA", 2.0, 0.0, 0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:badlands");
new BiomeMap(129, "SUNFLOWER_PLAINS", 0.8, 0.4, "minecraft:sunflower_plains");
new BiomeMap(130, "DESERT_MOUNTAINS", 2.0, 0.0, "minecraft:desert_lakes");
new BiomeMap(131, "EXTREME_HILLS_MOUNTAINS", 0.2, 0.3, "minecraft:gravelly_mountains");
new BiomeMap(132, "FLOWER_FOREST", 0.7, 0.8, "minecraft:flower_forest");
new BiomeMap(133, "TAIGA_MOUNTAINS", 0.05, 0.8, "minecraft:taiga_mountains");
new BiomeMap(140, "ICE_PLAINS_SPIKES", 0.0, 0.5, "minecraft:ice_spikes");
new BiomeMap(149, "JUNGLE_MOUNTAINS", 1.2, 0.9, "minecraft:modified_jungle");
new BiomeMap(151, "JUNGLE_EDGE_MOUNTAINS", 0.95, 0.8, "minecraft:modified_jungle_edge");
new BiomeMap(155, "BIRCH_FOREST_MOUNTAINS", 0.6, 0.6, "minecraft:tall_birch_forest");
new BiomeMap(156, "BIRCH_FOREST_HILLS_MOUNTAINS", 0.6, 0.6, "minecraft:tall_birch_hills");
new BiomeMap(157, "ROOFED_FOREST_MOUNTAINS", 0.7, 0.8, 0xFFFFFF, 0x28340A, 0, "minecraft:dark_forest_hills");
new BiomeMap(158, "COLD_TAIGA_MOUNTAINS", -0.5, 0.4, "minecraft:snowy_taiga_mountains");
new BiomeMap(160, "MEGA_SPRUCE_TAIGA", 0.25, 0.8, "minecraft:giant_spruce_taiga");
new BiomeMap(161, "MEGA_SPRUCE_TAIGA_HILLS", 0.3, 0.8, "minecraft:giant_spruce_taiga_hills");
new BiomeMap(162, "EXTREME_HILLS_PLUS_MOUNTAINS", 0.2, 0.3, "minecraft:modified_gravelly_mountains");
new BiomeMap(163, "SAVANNA_MOUNTAINS", 1.2, 0.0, "minecraft:shattered_savanna");
new BiomeMap(164, "SAVANNA_PLATEAU_MOUNTAINS", 1.0, 0.0, "minecraft:shattered_savanna_plateau");
new BiomeMap(165, "MESA_BRYCE", 2.0, 0.0,0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:eroded_badlands");
}
if (HDBlockModels.checkVersionRange(mcver, "1.7.0-1.17.1")) {
new BiomeMap(38, "MESA_PLATEAU_FOREST", 2.0, 0.0, 0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:wooded_badlands_plateau");
new BiomeMap(39, "MESA_PLATEAU", 2.0, 0.0, 0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:badlands_plateau");
new BiomeMap(134, "SWAMPLAND_MOUNTAINS", 0.8, 0.9, 0xE0FFAE, 0x2e282a, 0x902c52, "minecraft:swamp_hills");
new BiomeMap(166, "MESA_PLATEAU_FOREST_MOUNTAINS", 2.0, 0.0,0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:modified_wooded_badlands_plateau");
new BiomeMap(167, "MESA_PLATEAU_MOUNTAINS", 2.0, 0.0,0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:modified_badlands_plateau");
}
if (HDBlockModels.checkVersionRange(mcver, "1.9.0-")) {
new BiomeMap(127, "THE_VOID", "minecraft:the_void");
}
if (HDBlockModels.checkVersionRange(mcver, "1.13.0-")) {
new BiomeMap(40, "SMALL_END_ISLANDS", "minecraft:small_end_islands");
new BiomeMap(41, "END_MIDLANDS", "minecraft:end_midlands");
new BiomeMap(42, "END_HIGHLANDS", "minecraft:end_highlands");
new BiomeMap(43, "END_BARRENS", "minecraft:end_barrens");
new BiomeMap(44, "WARM_OCEAN", "minecraft:warm_ocean");
new BiomeMap(45, "LUKEWARM_OCEAN", "minecraft:lukewarm_ocean");
new BiomeMap(46, "COLD_OCEAN", "minecraft:cold_ocean");
new BiomeMap(47, "DEEP_WARM_OCEAN", "minecraft:deep_warm_ocean");
new BiomeMap(48, "DEEP_LUKEWARM_OCEAN", "minecraft:deep_lukewarm_ocean");
new BiomeMap(49, "DEEP_COLD_OCEAN", "minecraft:deep_cold_ocean");
new BiomeMap(50, "DEEP_FROZEN_OCEAN", "minecraft:deep_frozen_ocean");
}
if (HDBlockModels.checkVersionRange(mcver, "1.14.0-")) {
new BiomeMap(168, "BAMBOO_JUNGLE", "minecraft:bamboo_jungle");
new BiomeMap(169, "BAMBOO_JUNGLE_HILLS", "minecraft:bamboo_jungle_hills");
}
if (HDBlockModels.checkVersionRange(mcver, "1.16.0-")) {
new BiomeMap(170, "SOUL_SAND_VALLEY", "minecraft:soul_sand_valley");
new BiomeMap(171, "CRIMSON_FOREST", "minecraft:crimson_forest");
new BiomeMap(172, "WARPED_FOREST", "minecraft:warped_forest");
new BiomeMap(173, "BASALT_DELTAS", "minecraft:basalt_deltas");
}
if (HDBlockModels.checkVersionRange(mcver, "1.17.0-")) {
new BiomeMap(174, "DRIPSTONE_CAVES", "minecraft:dripstone_caves");
new BiomeMap(175, "LUSH_CAVES", "minecraft:lush_caves");
}
if (HDBlockModels.checkVersionRange(mcver, "1.18.0-")) {
new BiomeMap(38, "MESA_FOREST", 2.0, 0.0, 0xFFFFFF, 0x624c46, 0x8e5e70, "minecraft:wooded_badlands");
}
loadDone = true;
}
static {
for (int i = 0; i < biome_by_index.length; i++) {
BiomeMap bm = BiomeMap.byBiomeID(i-1);
if (bm == null) {
bm = new BiomeMap(i-1, "BIOME_" + (i-1));
bm.isDef = true;
}
}
}
private static boolean isUniqueID(String id) {
return !biome_ids.contains(id);
}
/**
* Encodes a grass/foliage color multiplier for efficient hot-path dispatch:
* == 0 0 (passthrough: return raw value unchanged)
* 0 < val 0xFFFFFF positive (blend: average with raw)
* val > 0xFFFFFF -(val & 0xFFFFFF) (negative sentinel: fixed override color)
*/
private static int encodeColorMult(int val) {
return (val > 0xFFFFFF) ? -(val & 0xFFFFFF) : val;
}
private static void resizeIfNeeded(int idx) {
if ((idx >= biome_by_index.length) ) {
int oldlen = biome_by_index.length;
biome_by_index = Arrays.copyOf(biome_by_index, idx * 3 / 2);
for (int i = oldlen; i < biome_by_index.length; i++) {
if (biome_by_index[i] == null) {
BiomeMap bm = new BiomeMap(i-1, "BIOME_" + (i-1));
bm.isDef = true;
}
}
}
}
private BiomeMap(int idx, String id, double tmp, double rain, int waterColorMultiplier, int grassmult, int foliagemult, String rl) {
/* Clamp values : we use raw values from MC code, which are clamped during color mapping only */
setTemperature(tmp);
setRainfall(rain);
this.watercolormult = waterColorMultiplier;
this.grassmult = encodeColorMult(grassmult);
this.foliagemult = encodeColorMult(foliagemult);
// Handle null biome
if (id == null) { id = "biome_" + idx; }
id = id.toUpperCase().replace(' ', '_');
if (isUniqueID(id) == false) {
id = id + "_" + idx;
}
this.id = id;
biome_ids.add(this.id);
// If index is NO_INDEX, find one after the well known ones
if (idx == NO_INDEX) {
idx = LAST_WELL_KNOWN;
while (true) {
idx++;
resizeIfNeeded(idx);
if (biome_by_index[idx].isDef) {
break;
}
}
}
else {
idx++; /* Insert one after ID value - null is zero index */
}
this.index = idx;
if (idx >= 0) {
resizeIfNeeded(idx);
biome_by_index[idx] = this;
}
this.resourcelocation = rl;
if (rl != null) {
biome_by_rl.put(rl, this);
}
}
public BiomeMap(int idx, String id) {
this(idx, id, 0.5, 0.5, 0xFFFFFF, 0, 0, null);
}
public BiomeMap(int idx, String id, String rl) {
this(idx, id, 0.5, 0.5, 0xFFFFFF, 0, 0, rl);
}
public BiomeMap(int idx, String id, double tmp, double rain) {
this(idx, id, tmp, rain, 0xFFFFFF, 0, 0, null);
}
public BiomeMap(int idx, String id, double tmp, double rain, String rl) {
this(idx, id, tmp, rain, 0xFFFFFF, 0, 0, rl);
}
public BiomeMap(String id, double tmp, double rain, String rl) {
this(NO_INDEX, id, tmp, rain, 0xFFFFFF, 0, 0, rl); // No index
}
private final int biomeLookup(int width) {
int w = width-1;
int t = (int)((1.0-tmp)*w);
int h = (int)((1.0 - (tmp*rain))*w);
return width*h + t;
}
public final int biomeLookup() {
return this.biomeindex256;
}
public final int getModifiedGrassMultiplier(int rawgrassmult) {
if (grassmult == 0) return rawgrassmult; // common case: no override
if (grassmult < 0) return -grassmult; // fixed color (pre-masked at set-time)
return ((rawgrassmult & 0xfefefe) + grassmult) >> 1; // blend
}
public final int getModifiedFoliageMultiplier(int rawfoliagemult) {
if (foliagemult == 0) return rawfoliagemult; // common case: no override
if (foliagemult < 0) return -foliagemult; // fixed color (pre-masked at set-time)
return ((rawfoliagemult & 0xfefefe) + foliagemult) >> 1; // blend
}
public final int getWaterColorMult() {
return watercolormult;
}
public final int ordinal() {
return index;
}
public static final BiomeMap byBiomeID(int idx) {
idx++;
if((idx >= 0) && (idx < biome_by_index.length))
return biome_by_index[idx];
else
return NULL;
}
public static final BiomeMap byBiomeResourceLocation(String resloc) {
BiomeMap b = biome_by_rl.get(resloc);
return (b != null) ? b : NULL;
}
public int getBiomeID() {
return index - 1; // Index of biome in MC biome table
}
public static final BiomeMap[] values() {
return biome_by_index;
}
public void setWaterColorMultiplier(int watercolormult) {
this.watercolormult = watercolormult;
}
public void setGrassColorMultiplier(int grassmult) {
this.grassmult = encodeColorMult(grassmult);
}
public void setFoliageColorMultiplier(int foliagemult) {
this.foliagemult = encodeColorMult(foliagemult);
}
public void setTemperature(double tmp) {
if(tmp < 0.0) tmp = 0.0;
if(tmp > 1.0) tmp = 1.0;
this.tmp = tmp;
this.biomeindex256 = this.biomeLookup(256);
}
public void setRainfall(double rain) {
if(rain < 0.0) rain = 0.0;
if(rain > 1.0) rain = 1.0;
this.rain = rain;
this.biomeindex256 = this.biomeLookup(256);
}
public final double getTemperature() {
return this.tmp;
}
public final double getRainfall() {
return this.rain;
}
public boolean isDefault() {
return isDef;
}
public String getId() {
return id;
}
public String toString() {
return String.format("%s(%s)", id, resourcelocation);
}
public @SuppressWarnings("unchecked") <T> Optional<T> getBiomeObject() {
return (Optional<T>) biomeObj;
}
public void setBiomeObject(Object biomeObj) {
this.biomeObj = Optional.of(biomeObj);
}
}

View file

@ -0,0 +1,37 @@
package org.dynmap.common;
public enum DynmapChatColor {
BLACK(0x0),
DARK_BLUE(0x1),
DARK_GREEN(0x2),
DARK_AQUA(0x3),
DARK_RED(0x4),
DARK_PURPLE(0x5),
GOLD(0x6),
GRAY(0x7),
DARK_GRAY(0x8),
BLUE(0x9),
GREEN(0xA),
AQUA(0xB),
RED(0xC),
LIGHT_PURPLE(0xD),
YELLOW(0xE),
WHITE(0xF);
private final String str;
private DynmapChatColor(final int code) {
this.str = String.format("\u00A7%x", code);
}
@Override
public String toString() {
return str;
}
public static String stripColor(final String input) {
if (input == null) {
return null;
}
return input.replaceAll("(?i)\u00A7[0-9A-Za-z]", "");
}
}

View file

@ -0,0 +1,31 @@
package org.dynmap.common;
public interface DynmapCommandSender {
/**
* Does command sender have given security privilege
* @param privid - privilege ID
* @return true if it does, false if it doesn't
*/
public boolean hasPrivilege(String privid);
/**
* Send given message to command sender
* @param msg - message to be sent (with color codes marked &amp;0 to &amp;F)
*/
public void sendMessage(String msg);
/**
* Test if command sender is still connected/online
* @return true if connected, false if not
*/
public boolean isConnected();
/**
* Is operator privilege
* @return true if operator
*/
public boolean isOp();
/**
* Test for permission node (no dynmap. prefix assumed)
* @param node - permission ID
* @return true if allowed
*/
public boolean hasPermissionNode(String node);
}

View file

@ -0,0 +1,146 @@
package org.dynmap.common;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
/**
* Simple handler for managing event listeners and dispatch in a neutral fashion
*
*/
public class DynmapListenerManager {
private DynmapCore core;
public DynmapListenerManager(DynmapCore core) {
this.core = core;
}
public interface EventListener {
}
public interface WorldEventListener extends EventListener {
public void worldEvent(DynmapWorld w);
}
public interface PlayerEventListener extends EventListener {
public void playerEvent(DynmapPlayer p);
}
public interface ChatEventListener extends EventListener {
public void chatEvent(DynmapPlayer p, String msg);
}
public interface BlockEventListener extends EventListener {
public void blockEvent(String material, String w, int x, int y, int z);
}
public interface SignChangeEventListener extends EventListener {
public void signChangeEvent(String material, String w, int x, int y, int z, String[] lines, DynmapPlayer p);
}
public enum EventType {
WORLD_LOAD,
WORLD_UNLOAD,
WORLD_SPAWN_CHANGE,
PLAYER_JOIN,
PLAYER_QUIT,
PLAYER_BED_LEAVE,
PLAYER_CHAT,
BLOCK_BREAK,
SIGN_CHANGE
}
private Map<EventType, ArrayList<EventListener>> listeners = new EnumMap<EventType, ArrayList<EventListener>>(EventType.class);
public void addListener(EventType type, EventListener listener) {
synchronized(listeners) {
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) {
lst = new ArrayList<EventListener>();
listeners.put(type, lst);
core.getServer().requestEventNotification(type);
}
lst.add(listener);
}
}
public void processWorldEvent(EventType type, DynmapWorld w) {
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) return;
int sz = lst.size();
for(int i = 0; i < sz; i++) {
EventListener el = lst.get(i);
if(el instanceof WorldEventListener) {
try {
((WorldEventListener)el).worldEvent(w);
} catch (Throwable t) {
Log.warning("processWorldEvent(" + type + "," + w + ") - exception", t);
}
}
}
}
public void processPlayerEvent(EventType type, DynmapPlayer p) {
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) return;
int sz = lst.size();
for(int i = 0; i < sz; i++) {
EventListener el = lst.get(i);
if(el instanceof PlayerEventListener) {
try {
((PlayerEventListener)el).playerEvent(p);
} catch (Throwable t) {
Log.warning("processPlayerEvent(" + type + "," + p + ") - exception", t);
}
}
}
}
public void processChatEvent(EventType type, DynmapPlayer p, String msg) {
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) return;
int sz = lst.size();
for(int i = 0; i < sz; i++) {
EventListener el = lst.get(i);
if(el instanceof ChatEventListener) {
try {
((ChatEventListener)el).chatEvent(p, msg);
} catch (Throwable t) {
Log.warning("processChatEvent(" + type + "," + msg + ") - exception", t);
}
}
}
}
public void processBlockEvent(EventType type, String material, String world, int x, int y, int z)
{
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) return;
int sz = lst.size();
for(int i = 0; i < sz; i++) {
EventListener el = lst.get(i);
if(el instanceof BlockEventListener) {
try {
((BlockEventListener)el).blockEvent(material, world, x, y, z);
} catch (Throwable t) {
Log.warning("processBlockEvent(" + type + "," + material + "," + world + "," + x + "," + y + "," + z + ") - exception", t);
}
}
}
}
public void processSignChangeEvent(EventType type, String material, String world, int x, int y, int z, String[] lines, DynmapPlayer p)
{
ArrayList<EventListener> lst = listeners.get(type);
if(lst == null) return;
int sz = lst.size();
for(int i = 0; i < sz; i++) {
EventListener el = lst.get(i);
if(el instanceof SignChangeEventListener) {
try {
((SignChangeEventListener)el).signChangeEvent(material, world, x, y, z, lines, p);
} catch (Throwable t) {
Log.warning("processSignChangeEvent(" + type + "," + material + "," + world + "," + x + "," + y + "," + z + ") - exception", t);
}
}
}
}
/* Clean up registered listeners */
public void cleanup() {
for(ArrayList<EventListener> l : listeners.values())
l.clear();
listeners.clear();
}
}

View file

@ -0,0 +1,111 @@
package org.dynmap.common;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.dynmap.DynmapLocation;
/**
* Player (server neutral) - represents online or offline player
*/
public interface DynmapPlayer extends DynmapCommandSender {
/**
* Get player ID
* @return ID (case insensitive)
*/
public String getName();
/**
* Get player display name
* @return display name
*/
public String getDisplayName();
/**
* Is player online?
* @return true if online
*/
public boolean isOnline();
/**
* Get current location of player
* @return location
*/
public DynmapLocation getLocation();
/**
* Get world ID of player
* @return id
*/
public String getWorld();
/**
* Get connected address for player
* @return connection address, or null if unknown
*/
public InetSocketAddress getAddress();
/**
* Check if player is sneaking
* @return true if sneaking
*/
public boolean isSneaking();
/**
* get spectator gamemode
* @return true if gamemode spectator
*/
public boolean isSpectator();
/**
* Get health
* @return health points
*/
public double getHealth();
/**
* Get armor points
* @return armor points
*/
public int getArmorPoints();
/**
* Get spawn bed location
* @return bed location, or null if none
*/
public DynmapLocation getBedSpawnLocation();
/**
* Get last login time
* @return UTC time (msec) of last login
*/
public long getLastLoginTime();
/**
* Get first login time
* @return UTC time (msec) of first login
*/
public long getFirstLoginTime();
/**
* Is invisible
* @return true if invisible
*/
public boolean isInvisible();
/**
* Get sort weight (ordered lowest to highest in player list: 0=default)
* @return sort weight
*/
public int getSortWeight();
/**
* Set sort weight (ordered lowest to highest in player list: 0=default)
* @param wt - sort weight
*/
public void setSortWeight(int wt);
/**
* Get skin URL for player
* @return URL, or null if not available
*/
public default String getSkinURL() { return null; }
/**
* Get player UUID
* Return UUID, or null if not available
*/
public default UUID getUUID() { return null; }
/**
* Send title and subtitle text (called from server thread)
*/
public default void sendTitleText(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTIcks) {
// Fallback if not implemented
if (title != null) this.sendMessage(title);;
if (subtitle != null) this.sendMessage(subtitle);
}
}

View file

@ -0,0 +1,253 @@
package org.dynmap.common;
import java.io.File;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapWorld;
import org.dynmap.common.DynmapListenerManager.EventType;
import org.dynmap.utils.MapChunkCache;
/**
* This interface defines a server-neutral interface for the DynmapCore and other neutral components to use to access server provided
* services. Platform-specific plugin must supply DynmapCore with an instance of an object implementing this interface.
*/
public abstract class DynmapServerInterface {
/**
* Schedule task to run on server-safe thread (one suitable for other server API calls)
* @param run - runnable method
* @param delay - delay in server ticks (50msec)
*/
public abstract void scheduleServerTask(Runnable run, long delay);
/**
* Call method on server-safe thread
* @param task - Callable method
* @param <T> - return value type for method called
* @return future for completion of call
*/
public abstract <T> Future<T> callSyncMethod(Callable<T> task);
/**
* Get list of online players
* @return list of online players
*/
public abstract DynmapPlayer[] getOnlinePlayers();
/**
* Request reload of plugin
*/
public abstract void reload();
/**
* Get active player
* @param name - player name
* @return player
*/
public abstract DynmapPlayer getPlayer(String name);
/**
* Get offline player
* @param name - player name
* @return player (offline or not)
*/
public abstract DynmapPlayer getOfflinePlayer(String name);
/**
* Get banned IPs
* @return set of banned IPs
*/
public abstract Set<String> getIPBans();
/**
* Get server name
* @return server name
*/
public abstract String getServerName();
/**
* Test if player ID is banned
* @param pid - player ID
* @return true if banned
*/
public abstract boolean isPlayerBanned(String pid);
/**
* Strip out chat color
* @param s - string to strip
* @return string stripped of color codes
*/
public abstract String stripChatColor(String s);
/**
* Request notificiation for given events (used by DynmapListenerManager)
* @param type - event type
* @return true if successful
*/
public abstract boolean requestEventNotification(EventType type);
/**
* Send notification of web chat message
* @param source - source
* @param name - name
* @param msg - message text
* @return true if not cancelled
*/
public abstract boolean sendWebChatEvent(String source, String name, String msg);
/**
* Broadcast message to players
* @param msg - message
*/
public abstract void broadcastMessage(String msg);
/**
* Get Biome ID lis
* @return list of biome IDs
*/
public abstract String[] getBiomeIDs();
/**
* Get snapshot cache hit rate
* @return hit rate
*/
public abstract double getCacheHitRate();
/**
* Reset cache stats
*/
public abstract void resetCacheStats();
/**
* Get world by name
* @param wname - world name
* @return world object, or null if not found
*/
public abstract DynmapWorld getWorldByName(String wname);
/**
* Test which of given set of permisssions a possibly offline user has
* @param player - player
* @param perms - set of permission IDs
* @return set of permission IDs allowed to player
*/
public abstract Set<String> checkPlayerPermissions(String player, Set<String> perms);
/**
* Test single permission attribute
* @param player - player
* @param perm - permission ID
* @return true if permitted
*/
public abstract boolean checkPlayerPermission(String player, String perm);
/**
* Render processor helper - used by code running on render threads to request chunk snapshot cache
* @param w - world
* @param chunks - list of chunks
* @param blockdata - include block data, if true
* @param highesty - include highest Y, if true
* @param biome - include biome data, if true
* @param rawbiome - include raw biome data, if true
* @return chunk map
*/
public abstract MapChunkCache createMapChunkCache(DynmapWorld w, List<DynmapChunk> chunks,
boolean blockdata, boolean highesty, boolean biome, boolean rawbiome);
/**
* Get maximum player count
* @return maximum online players
*/
public abstract int getMaxPlayers();
/**
* Get current player count
* @return number of online players
*/
public abstract int getCurrentPlayers();
/**
* Test if given mod is loaded (Forge)
* @param name - mod name
* @return true if mod loaded
*/
public boolean isModLoaded(String name) {
return false;
}
/**
* Get version of mod with given name
*
* @param name - name of mod
* @return version, or null of not found
*/
public String getModVersion(String name) {
return null;
}
/**
* Get block ID at given coordinate in given world (if chunk is loaded)
* @param wname - world name
* @param x - X coordinate
* @param y - Y coordinate
* @param z - Z coordinate
* @return block ID, or -1 if chunk at given coordinate isn't loaded
*/
public abstract int getBlockIDAt(String wname, int x, int y, int z);
/**
* Checks if a sign is at a given coordinate in a given world (if chunk is loaded)
* @param wname - world name
* @param x - X coordinate
* @param y - Y coordinate
* @param z - Z coordinate
* @return 1 if a sign is at the location, 0 if it's not, -1 if the chunk isn't loaded
*/
public abstract int isSignAt(String wname, int x, int y, int z);
/**
* Get current TPS for server (20.0 is nominal)
* @return ticks per second
*/
public abstract double getServerTPS();
/**
* Get address configured for server
*
* @return "" or null if none configured
*/
public abstract String getServerIP();
/**
* Get file/directory for given mod (for loading mod resources)
* @param mod - mod name
* @return file or directory, or null if not loaded
*/
public File getModContainerFile(String mod) {
return null;
}
/**
* Get mod list
* @return list of mods
*/
public List<String> getModList() {
return Collections.emptyList();
}
/**
* Get block ID map (modID:blockname, keyed by block ID)
* @return block ID map
*/
public Map<Integer, String> getBlockIDMap() {
return Collections.emptyMap();
}
/**
* Open resource (check all mods)
* @param modid - mod id
* @param rname - resource namep
* @return stream, or null
*/
public InputStream openResource(String modid, String rname) {
return null;
}
/**
* Get block unique ID map (module:blockid)
* @return block unique ID map
*/
public Map<String, Integer> getBlockUniqueIDMap() {
return Collections.emptyMap();
}
/**
* Get item unique ID map (module:itemid)
* @return item unique ID map
*/
public Map<String, Integer> getItemUniqueIDMap() {
return Collections.emptyMap();
}
/**
* Test if current thread is server thread
* @return true if server thread
*/
public boolean isServerThread() {
return false;
}
}

View file

@ -0,0 +1,5 @@
package org.dynmap.common.chunk;
public interface GenericBitStorage {
public int get(int idx);
}

View file

@ -0,0 +1,234 @@
package org.dynmap.common.chunk;
import java.util.Arrays;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.common.BiomeMap;
// Generic chunk representation
public class GenericChunk {
public final int cx, cz; // Chunk coord (world coord / 16)
public final GenericChunkSection[] sections;
public final int sectionCnt;
public final int cy_min; // CY value of first section in sections list (index = (Y >> 4) - cy_min
public final long inhabitedTicks;
public final int dataVersion; // Version of chunk data loaded
public final String chunkStatus; // Chunk status of loaded chunk
public final boolean isEmpty; // All sections are empty
private GenericChunk(int cx, int cz, int cy_min, GenericChunkSection[] sections, long inhabTicks, int dataversion, String chunkstatus) {
this.cx = cx;
this.cz = cz;
this.inhabitedTicks = inhabTicks;
this.dataVersion = dataversion;
this.chunkStatus = chunkstatus;
this.sections = new GenericChunkSection[sections.length + 2]; // Add one empty at top and bottom
this.cy_min = cy_min - 1; // Include empty at bottom
Arrays.fill(this.sections, GenericChunkSection.EMPTY); // Fill all spots with empty, including pad on bottom/top
boolean empty = true;
for (int off = 0; off < sections.length; off++) {
if (sections[off] != null) { // If defined, set the section
this.sections[off+1] = sections[off];
empty = empty && sections[off].isEmpty;
}
}
this.sectionCnt = sections.length;
this.isEmpty = empty;
}
// Get section for given block Y coord
public final GenericChunkSection getSection(int y) {
int idx = (y >> 4) - this.cy_min;
if ((idx < 0) || (idx >= sections.length)) {
return GenericChunkSection.EMPTY;
}
return this.sections[idx];
}
public final DynmapBlockState getBlockType(int x, int y, int z) {
return getSection(y).blocks.getBlock(x, y, z);
}
public final DynmapBlockState getBlockType(GenericChunkPos pos) {
return getSection(pos.y).blocks.getBlock(pos);
}
public final int getBlockSkyLight(int x, int y, int z) {
return getSection(y).sky.getLight(x, y, z);
}
public final int getBlockSkyLight(GenericChunkPos pos) {
return getSection(pos.y).sky.getLight(pos);
}
public final int getBlockEmittedLight(int x, int y, int z) {
return getSection(y).emitted.getLight(x, y, z);
}
public final int getBlockEmittedLight(GenericChunkPos pos) {
return getSection(pos.y).emitted.getLight(pos);
}
public final BiomeMap getBiome(int x, int y, int z) {
return getSection(y).biomes.getBiome(x, y, z);
}
public final BiomeMap getBiome(GenericChunkPos pos) {
return getSection(pos.y).biomes.getBiome(pos);
}
public final boolean isSectionEmpty(int cy) {
return getSection(cy << 4).isEmpty;
}
public final long getInhabitedTicks() {
return inhabitedTicks;
}
public String toString() {
return String.format("chunk(%d,%d:%s,off=%d", cx, cz, Arrays.deepToString((sections)), cy_min);
}
// Builder for fabricating finalized chunk
public static class Builder {
int x;
int z;
int y_min;
GenericChunkSection[] sections;
long inhabTicks;
String chunkstatus;
int dataversion;
public Builder(int world_ymin, int world_ymax) {
reset(world_ymin, world_ymax);
}
public void reset(int world_ymin, int world_ymax) {
x = 0; z = 0;
y_min = world_ymin >> 4;
dataversion = 0;
chunkstatus = null;
int y_max = (world_ymax + 15) >> 4; // Round up
sections = new GenericChunkSection[y_max - y_min + 1]; // Range for all potential sections
}
// Set inhabited ticks
public Builder inhabitedTicks(long inh) {
this.inhabTicks = inh;
return this;
}
// Set section
public Builder addSection(int sy, GenericChunkSection sect) {
if ((sy >= y_min) && ((sy - y_min) < sections.length)) {
this.sections[sy - y_min] = sect;
}
return this;
}
// Set coordinates
public Builder coords(int sx, int sz) {
this.x = sx;
this.z = sz;
return this;
}
// Generate simple sky lighting (must be after all sections have been added)
public Builder generateSky() {
int sky[] = new int[256]; // ZX array
boolean nonOpaque[] = new boolean[256]; // ZX array of non opaque blocks (atten < 15)
Arrays.fill(sky, 15); // Start fully lit at top
GenericChunkSection.Builder bld = new GenericChunkSection.Builder();
boolean allzero = false;
// Make light array for each section, start from top
for (int i = (sections.length - 1); i >= 0; i--) {
GenericChunkSection sect = sections[i];
if (sect == null) continue;
if (allzero) { // Start section with all zero already, just zero it and move on
// Replace section with new all zero light
sections[i] = bld.buildFrom(sect, 0);
continue;
}
byte[] ssky = new byte[2048];
int allfullcnt = 0;
// Top to bottom
for (int y = 15; (y >= 0) && (!allzero); y--) {
int totalval = 0; // Use for allzero or allfull
int yidx = y << 7;
// Light next layer down
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int idx = (z << 4) + x;
int val = sky[idx];
DynmapBlockState bs = sect.blocks.getBlock(x, y, z); // Get block
int atten = bs.getLightAttenuation();
if ((atten > 0) && (val > 0)) {
val = (val >= atten) ? (val - atten) : 0;
sky[idx] = val;
}
nonOpaque[idx] = atten < 15;
totalval += val;
}
}
allzero = (totalval == 0);
boolean allfull = (totalval == (15 * 256));
if (allfull) allfullcnt++;
// If not all fully lit nor all zero, handle horizontal spread
if (! (allfull || allzero)) {
// Now do horizontal spread
boolean changed;
do {
changed = false;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int idx = (z << 4) + x;
int cur = sky[idx];
boolean curnonopaq = nonOpaque[idx];
if (x < 15) { // If not right edge, check X spread
int right = sky[idx+1];
boolean rightnonopaq = nonOpaque[idx+1];
// If spread right
if (rightnonopaq && ((cur - 1) > right)) {
sky[idx+1] = cur - 1; changed = true;
}
// If spread left
else if (curnonopaq && (cur < (right - 1))) {
sky[idx] = cur = right - 1; changed = true;
}
}
if (z < 15) { // If not bottom edge, check Z spread
int down = sky[idx+16];
boolean downnonopaq = nonOpaque[idx+16];
// If spread down
if (downnonopaq && ((cur - 1) > down)) {
sky[idx+16] = cur - 1; changed = true;
}
// If spread up
else if (curnonopaq && (cur < (down - 1))) {
sky[idx] = down - 1; changed = true;
}
}
}
}
} while (changed);
// Save values
for (int v = 0; v < 128; v++) {
ssky[yidx | v] = (byte)(sky[v << 1] | (sky[(v << 1) + 1] << 4));
}
}
else if (allfull) { // All light, just fill it
for (int v = 0; v < 128; v++) {
ssky[yidx | v] = (byte) 0xFF;
}
}
}
// Replace section with new one with new lighting
if (allfullcnt == 16) { // Just full?
sections[i] = bld.buildFrom(sect, 15);
}
else {
sections[i] = bld.buildFrom(sect, ssky);
}
}
return this;
}
// Set chunk status
public Builder chunkStatus(String chunkstat) {
this.chunkstatus = chunkstat;
return this;
}
// Set data version
public Builder dataVersion(int dataver) {
this.dataversion = dataver;
return this;
}
// Build chunk
public GenericChunk build() {
return new GenericChunk(x, z, y_min, sections, inhabTicks, dataversion, chunkstatus);
}
}
}

View file

@ -0,0 +1,194 @@
package org.dynmap.common.chunk;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.dynmap.utils.DynIntHashMap;
// Generic chunk cache
public class GenericChunkCache {
public static class ChunkCacheRec {
public GenericChunk ss;
public DynIntHashMap tileData;
};
private CacheHashMap snapcache;
private final Object snapcachelock;
private ReferenceQueue<ChunkCacheRec> refqueue;
private long cache_attempts;
private long cache_success;
private boolean softref;
// World name -> small integer ID, used to build long cache keys without String concatenation.
// Accessed only while holding snapcachelock.
private final HashMap<String, Integer> worldIds = new HashMap<String, Integer>();
private int nextWorldId = 0;
private static class CacheRec {
Reference<ChunkCacheRec> ref;
}
@SuppressWarnings("serial")
public class CacheHashMap extends LinkedHashMap<Long, CacheRec> {
private int limit;
private IdentityHashMap<Reference<ChunkCacheRec>, Long> reverselookup;
public CacheHashMap(int lim) {
super(16, (float)0.75, true);
limit = lim;
reverselookup = new IdentityHashMap<Reference<ChunkCacheRec>, Long>();
}
protected boolean removeEldestEntry(Map.Entry<Long, CacheRec> last) {
boolean remove = (size() >= limit);
if(remove && (last != null) && (last.getValue() != null)) {
reverselookup.remove(last.getValue().ref);
}
return remove;
}
}
/**
* Create snapshot cache
*/
public GenericChunkCache(int max_size, boolean softref) {
snapcachelock = new Object();
snapcache = new CacheHashMap(max_size);
refqueue = new ReferenceQueue<ChunkCacheRec>();
this.softref = softref;
}
/**
* Encode world name + chunk coords as a single long key.
* Worlds are assigned small integer IDs (10 bits) on first use.
* cx and cz are each encoded in 27 bits (signed, supports ±8M chunks / ±128M blocks).
* Must be called while holding snapcachelock.
*/
private long getKey(String w, int cx, int cz) {
Integer wid = worldIds.get(w);
if (wid == null) {
wid = nextWorldId++;
worldIds.put(w, wid);
}
return ((long)(wid & 0x3FF) << 54) | ((long)(cx & 0x7FFFFFF) << 27) | (long)(cz & 0x7FFFFFF);
}
/**
* Invalidate cached snapshot, if in cache
*/
public void invalidateSnapshot(String w, int x, int y, int z) {
synchronized(snapcachelock) {
long key = getKey(w, x>>4, z>>4);
CacheRec rec = (snapcache != null) ? snapcache.remove(key) : null;
if(rec != null) {
snapcache.reverselookup.remove(rec.ref);
rec.ref.clear();
}
}
//processRefQueue();
}
/**
* Invalidate cached snapshot, if in cache
*/
public void invalidateSnapshot(String w, int x0, int y0, int z0, int x1, int y1, int z1) {
synchronized(snapcachelock) {
for(int xx = (x0>>4); xx <= (x1>>4); xx++) {
for(int zz = (z0>>4); zz <= (z1>>4); zz++) {
long key = getKey(w, xx, zz);
CacheRec rec = (snapcache != null) ? snapcache.remove(key) : null;
if(rec != null) {
snapcache.reverselookup.remove(rec.ref);
rec.ref.clear();
}
}
}
}
//processRefQueue();
}
/**
* Look for chunk snapshot in cache
*/
public ChunkCacheRec getSnapshot(String w, int chunkx, int chunkz) {
processRefQueue();
ChunkCacheRec ss = null;
CacheRec rec;
synchronized(snapcachelock) {
long key = getKey(w, chunkx, chunkz);
rec = (snapcache != null) ? snapcache.get(key) : null;
if(rec != null) {
ss = rec.ref.get();
if(ss == null) {
snapcache.reverselookup.remove(rec.ref);
snapcache.remove(key);
}
}
}
cache_attempts++;
if(ss != null) cache_success++;
return ss;
}
/**
* Add chunk snapshot to cache
*/
public void putSnapshot(String w, int chunkx, int chunkz, ChunkCacheRec ss) {
processRefQueue();
CacheRec rec = new CacheRec();
if (softref)
rec.ref = new SoftReference<ChunkCacheRec>(ss, refqueue);
else
rec.ref = new WeakReference<ChunkCacheRec>(ss, refqueue);
synchronized(snapcachelock) {
long key = getKey(w, chunkx, chunkz);
CacheRec prevrec = (snapcache != null) ? snapcache.put(key, rec) : null;
if(prevrec != null) {
snapcache.reverselookup.remove(prevrec.ref);
}
snapcache.reverselookup.put(rec.ref, key);
}
}
/**
* Process reference queue
*/
private void processRefQueue() {
Reference<? extends ChunkCacheRec> ref;
while((ref = refqueue.poll()) != null) {
synchronized(snapcachelock) {
Long k = (snapcache != null) ? snapcache.reverselookup.remove(ref) : null;
if(k != null) {
snapcache.remove(k);
}
}
}
}
/**
* Get hit rate (percent)
*/
public double getHitRate() {
if(cache_attempts > 0) {
return (100.0*cache_success)/(double)cache_attempts;
}
return 0.0;
}
/**
* Reset cache stats
*/
public void resetStats() {
cache_attempts = cache_success = 0;
}
/**
* Cleanup
*/
public void cleanup() {
synchronized(snapcachelock) {
if(snapcache != null) {
snapcache.clear();
snapcache.reverselookup.clear();
snapcache.reverselookup = null;
snapcache = null;
}
}
}
}

View file

@ -0,0 +1,53 @@
package org.dynmap.common.chunk;
// Generic block location iterator - represents 3D position, but includes fast precomputed chunk and section offsets
public class GenericChunkPos {
public int x, y, z; // 3D world position
public int cx, cz; // 2D chunk position (x / 16, z / 16)
public int cy; // Vertical section index (Y / 16)
public int sx, sy, sz; // 3D section position (x % 16, y % 16, z % 16)
public int soffset; // Section offset (256 * sy) + (16 * sz) + sx
public int sdiv4offset; // Subsection offset (16 * (sy / 4)) + (4 * (sz / 4)) + (sx / 4) (3D biomes)
public GenericChunkPos(int x, int y, int z) {
setPos(x, y, z);
}
// Replace X value
public final void setX(int x) {
this.x = x;
this.cx = x >> 4;
this.sx = x & 0xF;
this.soffset = (sy << 8) | (sz << 4) | sx;
this.sdiv4offset = ((sy & 0xC) << 4) | (sz & 0xC) | ((sx & 0xC) >> 2);
}
// Replace Y value
public final void setY(int y) {
this.y = y;
this.cy = y >> 4;
this.sy = y & 0xF;
this.soffset = (sy << 8) | (sz << 4) | sx;
this.sdiv4offset = ((sy & 0xC) << 4) | (sz & 0xC) | ((sx & 0xC) >> 2);
}
// Replace Z value
public final void setZ(int z) {
this.z = z;
this.cz = z >> 4;
this.sz = z & 0xF;
this.soffset = (sy << 8) | (sz << 4) | sx;
this.sdiv4offset = ((sy & 0xC) << 4) | (sz & 0xC) | ((sx & 0xC) >> 2);
}
// Replace X, Y, and Z values
public final void setPos(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
this.cx = x >> 4;
this.cy = y >> 4;
this.cz = z >> 4;
this.sx = x & 0xF;
this.sy = y & 0xF;
this.sz = z & 0xF;
this.soffset = (sy << 8) | (sz << 4) | sx;
this.sdiv4offset = ((sy & 0xC) << 4) | (sz & 0xC) | ((sx & 0xC) >> 2);
}
}

View file

@ -0,0 +1,367 @@
package org.dynmap.common.chunk;
import java.util.Arrays;
import org.dynmap.common.BiomeMap;
import org.dynmap.renderer.DynmapBlockState;
// Generic section: represents 16 x 16 x 16 grid of blocks
public class GenericChunkSection {
public final BiomeAccess biomes; // Access for biome data
public final BlockStateAccess blocks; // Access for block states
public final LightingAccess sky; // Access for sky light data
public final LightingAccess emitted; // Access for emitted light data
public final boolean isEmpty; // if true, section is all air with default sky and emitted light
// Block state access interface
public interface BlockStateAccess {
public DynmapBlockState getBlock(int x, int y, int z);
public DynmapBlockState getBlock(GenericChunkPos pos);
}
private static class BlockStateAccess3D implements BlockStateAccess {
private final DynmapBlockState blocks[]; // YZX order
// Array given to us by builder
BlockStateAccess3D(DynmapBlockState bs[]) {
blocks = bs;
}
public final DynmapBlockState getBlock(int x, int y, int z) {
return blocks[((y & 0xF) << 8) | ((z & 0xF) << 4) | (x & 0xF)];
}
public final DynmapBlockState getBlock(GenericChunkPos pos) {
return blocks[pos.soffset];
}
}
private static class BlockStateAccess3DPalette implements BlockStateAccess {
private final DynmapBlockState palette[];
private final short[] blocks; // YZX order
// Array given to us by builder
BlockStateAccess3DPalette(DynmapBlockState pal[], short[] blks) {
blocks = blks;
palette = pal;
}
public final DynmapBlockState getBlock(int x, int y, int z) {
return palette[blocks[((y & 0xF) << 8) | ((z & 0xF) << 4) | (x & 0xF)]];
}
public final DynmapBlockState getBlock(GenericChunkPos pos) {
return palette[blocks[pos.soffset]];
}
}
private static class BlockStateAccessSingle implements BlockStateAccess {
private final DynmapBlockState block;
BlockStateAccessSingle(DynmapBlockState bs) {
block = bs;
}
public final DynmapBlockState getBlock(int x, int y, int z) {
return block;
}
public final DynmapBlockState getBlock(GenericChunkPos pos) {
return block;
}
}
// Biome access interface
public interface BiomeAccess {
public BiomeMap getBiome(int x, int y, int z);
public BiomeMap getBiome(GenericChunkPos pos);
}
// For classic 2D biome map
private static class BiomeAccess2D implements BiomeAccess {
private final BiomeMap biomes[]; // (16 * Z) + X
// Array given to us by builder in right format
BiomeAccess2D(BiomeMap b[]) {
biomes = b;
}
public final BiomeMap getBiome(int x, int y, int z) {
return biomes[((z & 0xF) << 4) + (x & 0xF)];
}
public final BiomeMap getBiome(GenericChunkPos pos) {
return biomes[pos.soffset & 0xFF]; // Just ZX portion
}
public String toString() {
return String.format("Biome2D(%s)", Arrays.deepToString(biomes));
}
}
// For 3D biome map
private static class BiomeAccess3D implements BiomeAccess {
private final BiomeMap biomes[]; // (16 * (Y >> 2)) + (4 * (Z >> 2)) + (X >> 2)
// Array given to us by builder in right format (64 - YZX divided by 4)
BiomeAccess3D(BiomeMap[] b) {
biomes = b;
}
public final BiomeMap getBiome(int x, int y, int z) {
return biomes[ ((y & 0xC) << 2) | (z & 0xC) | ((x & 0xC) >> 2) ];
}
public final BiomeMap getBiome(GenericChunkPos pos) {
return biomes[pos.sdiv4offset];
}
public String toString() {
return String.format("Biome3D(%s)", Arrays.deepToString(biomes));
}
}
// For single biome map
private static class BiomeAccessSingle implements BiomeAccess {
private final BiomeMap biome;
BiomeAccessSingle(BiomeMap b) {
biome = b;
}
public final BiomeMap getBiome(int x, int y, int z) {
return biome;
}
public final BiomeMap getBiome(GenericChunkPos pos) {
return biome;
}
public String toString() {
return String.format("Biome1(%s)", biome);
}
}
// Lighting access interface
public interface LightingAccess {
public int getLight(int x, int y, int z);
public int getLight(GenericChunkPos pos);
}
private static class LightingAccess3D implements LightingAccess {
private final long[] light; // Nibble array (16 * y) * z (nibble at << (4*x))
// Construct using nibble array (same as lighting format in NBT fields) (128*Y + 8*Z + X/2) (oddX high, evenX low)
LightingAccess3D(byte[] lig) {
light = new long[256];
if (lig != null) {
for (int off = 0; (off < lig.length) && (off < 2048); off++) {
light[off >> 3] |= (0xFFL & (long)lig[off]) << ((off & 0x7) << 3);
}
}
}
public final int getLight(int x, int y, int z) {
return 0xF & (int)(light[((y & 0xF) << 4) | (z & 0xF)] >> ((x & 0xF) << 2));
}
public final int getLight(GenericChunkPos pos) {
return 0xF & (int)(light[pos.soffset >> 4] >> (4 * pos.sx));
}
}
private static class LightingAccessSingle implements LightingAccess {
private final int light;
LightingAccessSingle(int lig) {
light = lig & 0xF;
}
public final int getLight(int x, int y, int z) {
return light;
}
public final int getLight(GenericChunkPos pos) {
return light;
}
}
private GenericChunkSection(BlockStateAccess blks, BiomeAccess bio, LightingAccess skyac, LightingAccess emitac, boolean empty) {
blocks = blks;
biomes = bio;
sky = skyac;
emitted = emitac;
isEmpty = empty;
}
public String toString() {
return String.format("sect(bip:%s)", biomes);
}
private static BiomeAccess defaultBiome = new BiomeAccessSingle(BiomeMap.NULL);
private static BlockStateAccess defaultBlockState = new BlockStateAccessSingle(DynmapBlockState.AIR);
private static LightingAccess defaultLight = new LightingAccessSingle(0);
// Shared default empty section
public static final GenericChunkSection EMPTY = new GenericChunkSection(defaultBlockState, defaultBiome, new LightingAccessSingle(15), defaultLight, true);
// Factory for building section
public static class Builder {
private LightingAccess sk;
private LightingAccess em;
private DynmapBlockState bsaccumsing; // Used for single
private DynmapBlockState bsaccum[]; // Use for incremental setting of 3D - YZX order
private short[] bsblks; // Use for incremental setting of 3D palette - XZY order
private DynmapBlockState[] bspal; // Palette for bsblks
private BiomeMap baaccumsingle; // Use for single
private BiomeMap baaccum[]; // Use for incremental setting of 3D biome - YZX order or 2D biome (ZX order) length used to control which
private boolean empty;
// Initialize builder with empty state
public Builder() {
reset();
}
// Reset builder to default state
public void reset() {
bsaccumsing = DynmapBlockState.AIR;
bsaccum = null;
baaccumsingle = BiomeMap.NULL;
baaccum = null;
bsblks = null;
bspal = null;
sk = defaultLight;
em = defaultLight;
empty = true;
}
// Set sky lighting to single value
public Builder singleSkyLight(int val) {
sk = new LightingAccessSingle(val);
return this;
}
// Set sky lighting to given nibble array (YZX order)
public Builder skyLight(byte[] data) {
sk = new LightingAccess3D(data);
return this;
}
// Set emitted lighting to single value
public Builder singleEmittedLight(int val) {
em = new LightingAccessSingle(val);
return this;
}
// Set emitted lighting to given nibble array (YZX order)
public Builder emittedLight(byte[] data) {
em = new LightingAccess3D(data);
return this;
}
// Set bipme to single value
public Builder singleBiome(BiomeMap bio) {
baaccumsingle = bio;
baaccum = null;
return this;
}
// Set bipme for 2D style
public Builder xzBiome(int x, int z, BiomeMap bio) {
if ((baaccum == null) || (baaccum.length != 256)) {
baaccum = new BiomeMap[256];
Arrays.fill(baaccum, BiomeMap.NULL);
baaccumsingle = BiomeMap.NULL;
}
baaccum[((z & 0xF) << 4) + (x & 0xF)] = bio;
return this;
}
// Set bipme to 3D style
public Builder xyzBiome(int xdiv4, int ydiv4, int zdiv4, BiomeMap bio) {
if ((baaccum == null) || (baaccum.length != 64)) {
baaccum = new BiomeMap[64];
Arrays.fill(baaccum, BiomeMap.NULL);
baaccumsingle = BiomeMap.NULL;
}
baaccum[((ydiv4 & 0x3) << 4) + ((zdiv4 & 0x3) << 2) + (xdiv4 & 0x3)] = bio;
return this;
}
// Set block state to single value
public Builder singleBlockState(DynmapBlockState block) {
bsaccumsing = block;
bsaccum = null;
bsblks = null;
bspal = null;
empty = block.isAir();
return this;
}
// Set block state
public Builder xyzBlockState(int x, int y, int z, DynmapBlockState block) {
if (bsaccum == null) {
bsaccum = new DynmapBlockState[4096];
Arrays.fill(bsaccum, DynmapBlockState.AIR);
bsaccumsing = DynmapBlockState.AIR;
}
bsaccum[((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF)] = block;
empty = false;
return this;
}
// Set block state palette (states will be indexes vs this
public Builder xyzBlockStatePalette(DynmapBlockState[] bspalette) {
if (bsblks == null) {
bsblks = new short[4096];
}
bspal = Arrays.copyOf(bspalette, bspalette.length);
return this;
}
// Set block state using palette
public Builder xyzBlockStateInPalette(int x, int y, int z, short palidx) {
if (bsblks == null) {
bsblks = new short[4096];
}
bsblks[((y & 0xF) << 8) + ((z & 0xF) << 4) + (x & 0xF)] = palidx;
empty = false;
return this;
}
// Build copy from existing section with new skylight (YZX nibble array)
public GenericChunkSection buildFrom(GenericChunkSection s, byte[] sky) {
LightingAccess skyA = new LightingAccess3D(sky);
return new GenericChunkSection(s.blocks, s.biomes, skyA, s.emitted, s.isEmpty);
}
// Build copy from existing section with new single value skylight
public GenericChunkSection buildFrom(GenericChunkSection s, int singlesky) {
LightingAccess skyA = new LightingAccessSingle(singlesky);
return new GenericChunkSection(s.blocks, s.biomes, skyA, s.emitted, s.isEmpty);
}
// Build section based on current builder state
public GenericChunkSection build() {
// Process state access - see if we can reduce
if (bsaccum != null) {
DynmapBlockState v = bsaccum[0]; // Get first
boolean mismatch = false;
for (int i = 0; i < bsaccum.length; i++) {
if (bsaccum[i] != v) {
mismatch = true;
break;
}
}
if (!mismatch) { // All the same?
bsaccumsing = v;
bsaccum = null;
}
}
BlockStateAccess bs;
if (bsaccum != null) {
bs = new BlockStateAccess3D(bsaccum);
bsaccum = null;
empty = false;
}
else if (bspal != null) { // 3D palette
// Only one state in palette?
if (bspal.length == 1) {
bs = new BlockStateAccessSingle(bspal[0]); // Just single
}
else {
bs = new BlockStateAccess3DPalette(bspal, bsblks);
}
bspal = null;
bsblks = null;
}
else if (bsaccumsing == DynmapBlockState.AIR) { // Just air?
bs = defaultBlockState;
empty = true;
}
else {
bs = new BlockStateAccessSingle(bsaccumsing);
bsaccumsing = DynmapBlockState.AIR;
}
// See if biome access can be reduced to single
if (baaccum != null) {
BiomeMap v = baaccum[0]; // Get first
boolean mismatch = false;
for (int i = 0; i < baaccum.length; i++) {
if (baaccum[i] != v) {
mismatch = true;
break;
}
}
if (!mismatch) { // All the same?
baaccumsingle = v;
baaccum = null;
}
}
BiomeAccess ba;
if (baaccum != null) {
if (baaccum.length == 64) { // 3D?
ba = new BiomeAccess3D(baaccum);
}
else {
ba = new BiomeAccess2D(baaccum);
}
baaccum = null;
}
else if (baaccumsingle == BiomeMap.NULL) { // Just null?
ba = defaultBiome;
}
else {
ba = new BiomeAccessSingle(baaccumsingle);
baaccumsingle = BiomeMap.NULL;
}
return new GenericChunkSection(bs, ba, sk, em, empty);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
package org.dynmap.common.chunk;
import java.util.Set;
// Generic interface for accessing an NBT Composite object
public interface GenericNBTCompound {
public final byte TAG_END = 0;
public final byte TAG_BYTE = 1;
public final byte TAG_SHORT = 2;
public final byte TAG_INT = 3;
public final byte TAG_LONG = 4;
public final byte TAG_FLOAT = 5;
public final byte TAG_DOUBLE = 6;
public final byte TAG_BYTE_ARRAY = 7;
public final byte TAG_STRING = 8;
public final byte TAG_LIST = 9;
public final byte TAG_COMPOUND = 10;
public final byte TAG_INT_ARRAY = 11;
public final byte TAG_LONG_ARRAY = 12;
public final byte TAG_ANY_NUMERIC = 99;
public Set<String> getAllKeys();
public boolean contains(String s);
public boolean contains(String s, int i);
public byte getByte(String s);
public short getShort(String s);
public int getInt(String s);
public long getLong(String s);
public float getFloat(String s);
public double getDouble(String s);
public String getString(String s);
public byte[] getByteArray(String s);
public int[] getIntArray(String s);
public long[] getLongArray(String s);
public GenericNBTCompound getCompound(String s);
public GenericNBTList getList(String s, int i);
public boolean getBoolean(String s);
public String getAsString(String s); /// get(s).getAsString()
// Factory for bit storage
public GenericBitStorage makeBitStorage(int bits, int count, long[] data);
}

View file

@ -0,0 +1,8 @@
package org.dynmap.common.chunk;
// Generic interface for accessing an NBT Composite object
public interface GenericNBTList {
public int size();
public String getString(int idx);
public GenericNBTCompound getCompound(int idx);
}

View file

@ -0,0 +1,31 @@
package org.dynmap.debug;
import java.util.ArrayList;
public class Debug {
private static ArrayList<Debugger> debuggers = new ArrayList<Debugger>();
public synchronized static void addDebugger(Debugger d) {
debuggers.add(d);
}
public synchronized static void removeDebugger(Debugger d) {
debuggers.remove(d);
}
public synchronized static void clearDebuggers() {
debuggers.clear();
}
public synchronized static void debug(String message) {
for(int i = 0; i < debuggers.size(); i++) debuggers.get(i).debug(message);
}
public synchronized static void error(String message) {
for(int i = 0; i < debuggers.size(); i++) debuggers.get(i).error(message);
}
public synchronized static void error(String message, Throwable thrown) {
for(int i = 0; i < debuggers.size(); i++) debuggers.get(i).error(message, thrown);
}
}

View file

@ -0,0 +1,9 @@
package org.dynmap.debug;
public interface Debugger {
void debug(String message);
void error(String message);
void error(String message, Throwable thrown);
}

View file

@ -0,0 +1,27 @@
package org.dynmap.debug;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
public class LogDebugger implements Debugger {
public LogDebugger(DynmapCore core, ConfigurationNode configuration) {
}
@Override
public void debug(String message) {
Log.info(message);
}
@Override
public void error(String message) {
Log.severe(message);
}
@Override
public void error(String message, Throwable thrown) {
Log.severe(message);
thrown.printStackTrace();
}
}

View file

@ -0,0 +1,21 @@
package org.dynmap.debug;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
public class NullDebugger implements Debugger {
public static final NullDebugger instance = new NullDebugger(null, null);
public NullDebugger(DynmapCore core, ConfigurationNode configuration) {
}
public void debug(String message) {
}
public void error(String message) {
}
public void error(String message, Throwable thrown) {
}
}

View file

@ -0,0 +1,363 @@
package org.dynmap.exporter;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapLocation;
import org.dynmap.DynmapWorld;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.common.DynmapPlayer;
import org.dynmap.hdmap.HDShader;
/**
* Handler for export commands (/dynmapexp)
*/
public class DynmapExpCommands {
private HashMap<String, ExportContext> sessions = new HashMap<String, ExportContext>();
private static class ExportContext {
public String shader = "stdtexture";
public int xmin = Integer.MIN_VALUE;
public int ymin = Integer.MIN_VALUE;
public int zmin = Integer.MIN_VALUE;
public int xmax = Integer.MIN_VALUE;
public int ymax = Integer.MIN_VALUE;
public int zmax = Integer.MIN_VALUE;
public String world;
public boolean groupByChunk;
public boolean groupByBlockID;
public boolean groupByBlockIDData;
public boolean groupByTexture;
}
private String getContextID(DynmapCommandSender sender) {
String id = "<console>";
if (sender instanceof DynmapPlayer) {
id = ((DynmapPlayer)sender).getName();
}
return id;
}
private ExportContext getContext(DynmapCommandSender sender) {
String id = getContextID(sender);
ExportContext ctx = sessions.get(id);
if (ctx == null) {
ctx = new ExportContext();
sessions.put(id, ctx);
}
return ctx;
}
public boolean processCommand(DynmapCommandSender sender, String cmd, String commandLabel, String[] args, DynmapCore core) {
/* Re-parse args - handle doublequotes */
args = DynmapCore.parseArgs(args, sender);
if(args.length < 1)
return false;
if(!core.checkPlayerPermission(sender, "dynmapexp.export")) {
return true;
}
cmd = args[0];
boolean rslt = false;
ExportContext ctx = getContext(sender);
if(cmd.equalsIgnoreCase("set")) {
rslt = handleSetExport(sender, args, ctx, core);
}
else if (cmd.equalsIgnoreCase("radius")) {
rslt = handleRadius(sender, args, ctx, core);
}
else if (cmd.equalsIgnoreCase("pos0")) {
rslt = handlePosN(sender, args, ctx, core, 0);
}
else if (cmd.equalsIgnoreCase("pos1")) {
rslt = handlePosN(sender, args, ctx, core, 1);
}
else if (cmd.equalsIgnoreCase("export")) {
rslt = handleDoExport(sender, args, ctx, core);
}
else if(cmd.equalsIgnoreCase("reset")) {
rslt = handleResetExport(sender, args, ctx, core);
}
else if(cmd.equalsIgnoreCase("purge")) {
rslt = handlePurgeExport(sender, args, ctx, core);
}
else if(cmd.equalsIgnoreCase("info")) {
rslt = handleInfo(sender, args, ctx, core);
}
return rslt;
}
public List<String> getTabCompletions(DynmapCommandSender sender, String[] args, DynmapCore core) {
/* Re-parse args - handle doublequotes */
args = DynmapCore.parseArgs(args, sender, true);
if (args == null || args.length <= 1) {
return Collections.emptyList();
}
String cmd = args[0];
if(cmd.equalsIgnoreCase("set")) {
List<String> keys = new ArrayList<>(
Arrays.asList("x0", "x1", "y0", "y1", "z0", "z1", "world", "shader", "byChunk",
"byBlockID", "byBlockIDData", "byTexture"));
if (args.length % 2 == 0) { // Args contain only complete key value argument pairs (plus subcommand)
// Remove previous used keys
for (int i = 1; i < args.length; i += 2) {
keys.remove(args[i]);
}
return keys;
} else { // Incomplete key value argument pair, suggest values
final String lastKey = args[args.length - 2];
final String lastValue = args[args.length - 1];
switch(lastKey) {
case "world":
return core.getWorldSuggestions(lastValue);
case "shader":
return MapManager.mapman.hdmapman.shaders.keySet().stream()
.filter(value -> value.startsWith(lastValue))
.collect(Collectors.toList());
case "byChunk":
case "byBlockID":
case "byBlockIDData":
case "byTexture":
return Stream.of("true", "false")
.filter(value -> value.startsWith(lastValue))
.collect(Collectors.toList());
}
}
}
return Collections.emptyList();
}
private boolean handleInfo(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
sender.sendMessage(String.format("Bounds: <%s,%s,%s> - <%s,%s,%s> on world '%s'", val(ctx.xmin), val(ctx.ymin), val(ctx.zmin),
val(ctx.xmax), val(ctx.ymax), val(ctx.zmax), ctx.world));
sender.sendMessage(String.format("groups: byChunk: %b, byBlockID: %b, byBlockIDData: %b, byTexture: %b", ctx.groupByChunk, ctx.groupByBlockID, ctx.groupByBlockIDData, ctx.groupByTexture));
return true;
}
private boolean handleSetExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
if (args.length < 3) {
sender.sendMessage(String.format("Bounds: <%s,%s,%s> - <%s,%s,%s> on world '%s'", val(ctx.xmin), val(ctx.ymin), val(ctx.zmin),
val(ctx.xmax), val(ctx.ymax), val(ctx.zmax), ctx.world));
return true;
}
for (int i = 1; i < (args.length-1); i += 2) {
try {
if (args[i].equals("x0")) {
ctx.xmin = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("x1")) {
ctx.xmax = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("y0")) {
ctx.ymin = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("y1")) {
ctx.ymax = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("z0")) {
ctx.zmin = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("z1")) {
ctx.zmax = Integer.parseInt(args[i+1]);
}
else if (args[i].equals("world")) {
DynmapWorld w = core.getWorld(args[i+1]);
if (w != null) {
ctx.world = args[i+1];
}
else {
sender.sendMessage("Invalid world '" + args[i+1] + "'");
return true;
}
}
else if (args[i].equals("shader")) {
HDShader s = MapManager.mapman.hdmapman.shaders.get(args[i+1]);
if (s == null) {
sender.sendMessage("Unknown shader '" + args[i+1] + "'");
return true;
}
ctx.shader = args[i+1];
}
else if (args[i].equals("byChunk")) {
ctx.groupByChunk = args[i+1].equalsIgnoreCase("true");
}
else if (args[i].equals("byBlockID")) {
ctx.groupByBlockID = args[i+1].equalsIgnoreCase("true");
}
else if (args[i].equals("byBlockIDData")) {
ctx.groupByBlockIDData = args[i+1].equalsIgnoreCase("true");
}
else if (args[i].equals("byTexture")) {
ctx.groupByTexture = args[i+1].equalsIgnoreCase("true");
}
else { // Unknown setting
sender.sendMessage("Unknown setting '" + args[i] + "'");
return true;
}
} catch (NumberFormatException nfx) {
sender.sendMessage("Invalid value for '" + args[i] + "' - " + args[i+1]);
return true;
}
}
return handleInfo(sender, args, ctx, core);
}
private boolean handleRadius(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
if ((sender instanceof DynmapPlayer) == false) { // Not a player
sender.sendMessage("Only usable by player");
return true;
}
DynmapPlayer plyr = (DynmapPlayer) sender;
DynmapLocation loc = plyr.getLocation();
DynmapWorld world = null;
if (loc != null) {
world = core.getWorld(loc.world);
}
if (world == null) {
sender.sendMessage("Location not found for player");
return true;
}
int radius = 16;
if (args.length >= 2) {
try {
radius = Integer.parseInt(args[1]);
if (radius < 0) {
sender.sendMessage("Invalid radius - " + args[1]);
return true;
}
} catch (NumberFormatException nfx) {
sender.sendMessage("Invalid radius - " + args[1]);
return true;
}
}
ctx.xmin = (int)Math.floor(loc.x) - radius;
ctx.xmax = (int)Math.ceil(loc.x) + radius;
ctx.zmin = (int)Math.floor(loc.z) - radius;
ctx.zmax = (int)Math.ceil(loc.z) + radius;
ctx.ymin = world.minY;
ctx.ymax = world.worldheight - 1;
ctx.world = world.getName();
return handleInfo(sender, args, ctx, core);
}
private boolean handlePosN(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core, int n) {
if ((sender instanceof DynmapPlayer) == false) { // Not a player
sender.sendMessage("Only usable by player");
return true;
}
DynmapPlayer plyr = (DynmapPlayer) sender;
DynmapLocation loc = plyr.getLocation();
DynmapWorld world = null;
if (loc != null) {
world = core.getWorld(loc.world);
}
if (world == null) {
sender.sendMessage("Location not found for player");
return true;
}
if (n == 0) {
ctx.xmin = (int)Math.floor(loc.x);
ctx.ymin = (int)Math.floor(loc.y);
ctx.zmin = (int)Math.floor(loc.z);
}
else {
ctx.xmax = (int)Math.floor(loc.x);
ctx.ymax = (int)Math.floor(loc.y);
ctx.zmax = (int)Math.floor(loc.z);
}
ctx.world = world.getName();
return handleInfo(sender, args, ctx, core);
}
private boolean handleDoExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
if ((ctx.world == null) || (ctx.xmin == Integer.MIN_VALUE) || (ctx.ymin == Integer.MIN_VALUE) ||
(ctx.zmin == Integer.MIN_VALUE) || (ctx.xmax == Integer.MIN_VALUE) || (ctx.ymax == Integer.MIN_VALUE) ||
(ctx.zmax == Integer.MIN_VALUE)) {
sender.sendMessage("Bounds not set");
return true;
}
DynmapWorld w = core.getWorld(ctx.world);
if (w == null) {
sender.sendMessage("Invalid world - " + ctx.world);
return true;
}
HDShader s = MapManager.mapman.hdmapman.shaders.get(ctx.shader);
if (s == null) {
sender.sendMessage("Invalid shader - " + ctx.shader);
return true;
}
String basename = "dynmapexp";
if (args.length > 1) {
basename = args[1];
}
basename = basename.replace('/', '_');
basename = basename.replace('\\', '_');
File f = new File(core.getExportFolder(), basename + ".zip");
int idx = 0;
String finalBasename = basename;
while (f.exists()) {
idx++;
finalBasename = basename + "_" + idx;
f = new File(core.getExportFolder(), finalBasename + ".zip");
}
sender.sendMessage("Exporting to " + f.getPath());
OBJExport exp = new OBJExport(f, s, w, core, finalBasename);
exp.setRenderBounds(ctx.xmin, ctx.ymin, ctx.zmin, ctx.xmax, ctx.ymax, ctx.zmax);
exp.setGroupEnabled(OBJExport.GROUP_CHUNK, ctx.groupByChunk);
exp.setGroupEnabled(OBJExport.GROUP_TEXTURE, ctx.groupByTexture);
exp.setGroupEnabled(OBJExport.GROUP_BLOCKID, ctx.groupByBlockID);
exp.setGroupEnabled(OBJExport.GROUP_BLOCKIDMETA, ctx.groupByBlockIDData);
MapManager.mapman.startOBJExport(exp, sender);
return true;
}
private boolean handleResetExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
sessions.remove(getContextID(sender));
return true;
}
private boolean handlePurgeExport(DynmapCommandSender sender, String[] args, ExportContext ctx, DynmapCore core) {
if (args.length > 1) {
String basename = args[1];
basename = basename.replace('/', '_');
basename = basename.replace('\\', '_');
File f = new File(core.getExportFolder(), basename + ".zip");
if (f.exists()) {
f.delete();
sender.sendMessage("Removed " + f.getPath());
}
else {
sender.sendMessage(f.getPath() + " not found");
}
}
return true;
}
private String val(int v) {
if (v == Integer.MIN_VALUE)
return "N/A";
else
return Integer.toString(v);
}
}

View file

@ -0,0 +1,656 @@
package org.dynmap.exporter;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.hdmap.CustomBlockModel;
import org.dynmap.hdmap.HDBlockModels;
import org.dynmap.hdmap.HDBlockStateTextureMap;
import org.dynmap.hdmap.HDScaledBlockModels;
import org.dynmap.hdmap.HDShader;
import org.dynmap.hdmap.TexturePack.BlockTransparency;
import org.dynmap.renderer.CustomRenderer;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.renderer.RenderPatch;
import org.dynmap.renderer.RenderPatchFactory.SideVisible;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.IndexedVector3D;
import org.dynmap.utils.IndexedVector3DList;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.dynmap.utils.PatchDefinition;
import org.dynmap.utils.PatchDefinitionFactory;
public class OBJExport {
private final File destZipFile; // Destination ZIP file
private final HDShader shader; // Shader to be used for textures
private final DynmapWorld world; // World to be rendered
private final DynmapCore core;
private final String basename;
private int minX, minY, minZ; // Minimum world coordinates to be rendered
private int maxX, maxY, maxZ; // Maximum world coordinates to be rendered
private static Charset UTF8 = Charset.forName("UTF-8");
private ZipOutputStream zos; // Output stream ZIP for result
private double originX, originY, originZ; // Origin for exported model
private double scale = 1.0; // Scale for exported model
private boolean centerOrigin = true; // Center at origin
private PatchDefinition[] defaultPathces; // Default patches for solid block, indexed by BlockStep.ordinal()
private HashSet<String> matIDs = new HashSet<String>(); // Set of defined material ids for RP
private static class Face {
String groupLine;
String faceLine;
}
private HashMap<String, List<Face>> facesByTexture = new HashMap<String, List<Face>>();
private static final int MODELSCALE = 16;
private static final double BLKSIZE = 1.0 / (double) MODELSCALE;
// Index of group settings
public static final int GROUP_CHUNK = 0;
public static final int GROUP_TEXTURE = 1;
public static final int GROUP_BLOCKID = 2;
public static final int GROUP_BLOCKIDMETA = 3;
public static final int GROUP_COUNT = 4;
private String[] group = new String[GROUP_COUNT];
private boolean[] enabledGroups = new boolean[GROUP_COUNT];
private String groupline = null;
// Vertex set
private IndexedVector3DList vertices;
// UV set
private IndexedVector3DList uvs;
// Scaled models
private HDScaledBlockModels models;
public static final int ROT0 = 0;
public static final int ROT90 = 1;
public static final int ROT180 = 2;
public static final int ROT270 = 3;
public static final int HFLIP = 4;
private static final double[][] pp = {
{ 0, 0, 0, 1, 0, 0, 0, 0, 1 },
{ 0, 1, 1, 1, 1, 1, 0, 1, 0 },
{ 1, 0, 0, 0, 0, 0, 1, 1, 0 },
{ 0, 0, 1, 1, 0, 1, 0, 1, 1 },
{ 0, 0, 0, 0, 0, 1, 0, 1, 0 },
{ 1, 0, 1, 1, 0, 0, 1, 1, 1 }
};
/**
* Constructor for OBJ file export
* @param dest - destination file (ZIP)
* @param shader - shader to be used for coloring/texturing
* @param world - world to be rendered
* @param core - core object
* @param basename - base file name
*/
public OBJExport(File dest, HDShader shader, DynmapWorld world, DynmapCore core, String basename) {
destZipFile = dest;
this.shader = shader;
this.world = world;
this.core = core;
this.basename = basename;
this.defaultPathces = new PatchDefinition[6];
PatchDefinitionFactory fact = HDBlockModels.getPatchDefinitionFactory();
for (BlockStep s : BlockStep.values()) {
double[] p = pp[s.getFaceEntered()];
int ord = s.ordinal();
defaultPathces[ord] = fact.getPatch(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], 0, 1, 0, 0, 1, 1, SideVisible.TOP, ord);
}
vertices = new IndexedVector3DList(new IndexedVector3DList.ListCallback() {
@Override
public void elementAdded(IndexedVector3DList list, IndexedVector3D newElement) {
try {
/* Minecraft XYZ maps to OBJ YZX */
addStringToExportedFile(String.format(Locale.US, "v %.4f %.4f %.4f\n",
(newElement.x - originX) * scale,
(newElement.y - originY) * scale,
(newElement.z - originZ) * scale
));
} catch (IOException iox) {
}
}
});
uvs = new IndexedVector3DList(new IndexedVector3DList.ListCallback() {
@Override
public void elementAdded(IndexedVector3DList list, IndexedVector3D newElement) {
try {
addStringToExportedFile(String.format(Locale.US, "vt %.4f %.4f\n", newElement.x, newElement.y));
} catch (IOException iox) {
}
}
});
// Get models
models = HDBlockModels.getModelsForScale(MODELSCALE);
}
/**
* Set render bounds
*
* @param minx - minimum X coord
* @param miny - minimum Y coord
* @param minz - minimum Z coord
* @param maxx - maximum X coord
* @param maxy - maximum Y coord
* @param maxz - maximum Z coord
*/
public void setRenderBounds(int minx, int miny, int minz, int maxx, int maxy, int maxz) {
if (minx < maxx) {
minX = minx; maxX = maxx;
}
else {
minX = maxx; maxX = minx;
}
if (miny < maxy) {
minY = miny; maxY = maxy;
}
else {
minY = maxy; maxY = miny;
}
if (minz < maxz) {
minZ = minz; maxZ = maxz;
}
else {
minZ = maxz; maxZ = minz;
}
if (minY < world.minY) minY = world.minY;
if (maxY >= world.worldheight) maxY = world.worldheight - 1;
if (centerOrigin) {
originX = (maxX + minX) / 2.0;
originY = minY;
originZ = (maxZ + minZ) / 2.0;
}
}
/**
* Set origin for exported model
* @param ox - origin x
* @param oy - origin y
* @param oz - origin z
*/
public void setOrigin(double ox, double oy, double oz) {
originX = ox;
originY = oy;
originZ = oz;
centerOrigin = false;
}
/**
* Set scale for exported model
* @param scale = scale
*/
public void setScale(double scale) {
this.scale = scale;
}
/**
* Process export
*
* @param sender - command sender: use for feedback messages
* @return true if successful, false if not
*/
public boolean processExport(DynmapCommandSender sender) {
boolean good = false;
try {
// Open ZIP file destination
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destZipFile)));
List<DynmapChunk> requiredChunks = new ArrayList<DynmapChunk>();
int mincx = (minX >> 4);
int maxcx = (maxX + 15) >> 4;
int mincz = (minZ >> 4);
int maxcz = (maxZ + 15) >> 4;
boolean[] edgebits = new boolean[6];
startExportedFile(basename + ".obj");
// Add material library
addStringToExportedFile("mtllib " + basename + ".mtl\n");
// Loop through - do 8x8 chunks at a time (plus 1 border each way)
for (int cx = mincx; cx <= maxcx; cx += 4) {
for (int cz = mincz; cz <= maxcz; cz += 4) {
// Build chunk cache for block of chunks
requiredChunks.clear();
for (int i = -1; i < 5; i++) {
for (int j = -1; j < 5; j++) {
if (((cx+i) <= maxcx) && ((cz+j) <= maxcz) && ((cx+i) >= mincx) && ((cz+j) >= mincz)) {
requiredChunks.add(new DynmapChunk(cx + i, cz + j));
}
}
}
// Get the chunk buffer
MapChunkCache cache = core.getServer().createMapChunkCache(world, requiredChunks, true, false, true, false);
if (cache == null) {
throw new IOException("Error loading chunk cache");
}
MapIterator iter = cache.getIterator(minX, minY, minZ);
for (int x = cx * 16; (x < (cx * 16 + 64)) && (x <= maxX); x++) {
if (x < minX) x = minX;
edgebits[BlockStep.X_PLUS.ordinal()] = (x == minX);
edgebits[BlockStep.X_MINUS.ordinal()] = (x == maxX);
for (int z = cz * 16; (z < (cz * 16 + 64)) && (z <= maxZ); z++) {
if (z < minZ) z = minZ;
edgebits[BlockStep.Z_PLUS.ordinal()] = (z == minZ);
edgebits[BlockStep.Z_MINUS.ordinal()] = (z == maxZ);
iter.initialize(x, minY, z);
updateGroup(GROUP_CHUNK, "chunk" + (x >> 4) + "_" + (z >> 4));
// Do first (bottom)
edgebits[BlockStep.Y_MINUS.ordinal()] = true;
edgebits[BlockStep.Y_PLUS.ordinal()] = false;
DynmapBlockState blk = iter.getBlockType();
if (blk.isNotAir()) { // Not air
handleBlock(blk, iter, edgebits);
}
// Do middle
edgebits[BlockStep.Y_MINUS.ordinal()] = false;
for (int y = minY + 1; y < maxY; y++) {
iter.setY(y);
blk = iter.getBlockType();
if (blk.isNotAir()) { // Not air
handleBlock(blk, iter, edgebits);
}
}
// Do top
edgebits[BlockStep.Y_PLUS.ordinal()] = true;
iter.setY(maxY);
blk = iter.getBlockType();
if (blk.isNotAir()) { // Not air
handleBlock(blk, iter, edgebits);
}
}
}
// Output faces by texture
String grp = "";
for (String material : facesByTexture.keySet()) {
List<Face> faces = facesByTexture.get(material);
matIDs.add(material); // Record material use
addStringToExportedFile(String.format("usemtl %s\n", material));
for (Face face : faces) {
if ((face.groupLine != null) && (!face.groupLine.equals(grp))) {
grp = face.groupLine;
addStringToExportedFile(grp);
}
addStringToExportedFile(face.faceLine);
}
}
// Clear face table
facesByTexture.clear();
// Clean up vertices we've moved past
vertices.resetSet(minX, minY, minZ, cx * 16 + 64, maxY, cz * 16 + 64);
}
}
finishExportedFile();
// If shader provided, add shader content to ZIP
if (shader != null) {
sender.sendMessage("Adding textures from shader " + shader.getName());
shader.exportAsMaterialLibrary(sender, this);
sender.sendMessage("Texture export completed");
}
// And close the ZIP
zos.finish();
zos.close();
zos = null;
good = true;
sender.sendMessage("Export completed - " + destZipFile.getPath());
} catch (IOException iox) {
sender.sendMessage("Export failed: " + iox.getMessage());
} finally {
if (zos != null) {
try { zos.close(); } catch (IOException e) {}
zos = null;
destZipFile.delete();
}
}
return good;
}
/**
* Start adding file to export
* @param fname - path/name of file in destination zip
* @throws IOException if error starting file
*/
public void startExportedFile(String fname) throws IOException {
ZipEntry ze = new ZipEntry(fname);
zos.putNextEntry(ze);
}
/**
* Add bytes to current exported file
* @param buf - buffer with bytes
* @param off - offset of start
* @param len - length to be added
* @throws IOException if error adding to file
*/
public void addBytesToExportedFile(byte[] buf, int off, int len) throws IOException {
zos.write(buf, off, len);
}
/**
* Add string to curent exported file (UTF-8)
* @param str - string to be written
* @throws IOException if error adding to file
*/
public void addStringToExportedFile(String str) throws IOException {
byte[] b = str.getBytes(UTF8);
zos.write(b, 0, b.length);
}
/**
* Finish adding file to export
* @throws IOException if error completing file
*/
public void finishExportedFile() throws IOException {
zos.closeEntry();
}
/**
* Handle block at current iterator coord
* @param id - block ID
* @param iter - iterator
* @param edgebits - bit N corresponds to side N being an endge (forge render)
*/
private void handleBlock(DynmapBlockState blk, MapIterator map, boolean[] edgebits) throws IOException {
BlockStep[] steps = BlockStep.values();
int[] txtidx = null;
// See if the block has a patch model
RenderPatch[] patches = models.getPatchModel(blk);
/* If no patches, see if custom model */
if(patches == null) {
CustomBlockModel cbm = models.getCustomBlockModel(blk);
if (cbm != null) { /* If so, get our meshes */
patches = cbm.getMeshForBlock(map);
}
}
if (patches != null) {
steps = new BlockStep[patches.length];
txtidx = new int[patches.length];
for (int i = 0; i < txtidx.length; i++) {
txtidx[i] = ((PatchDefinition) patches[i]).getTextureIndex();
steps[i] = ((PatchDefinition) patches[i]).step;
}
}
else { // See if volumetric
short[] smod = models.getScaledModel(blk);
if (smod != null) {
patches = getScaledModelAsPatches(smod);
steps = new BlockStep[patches.length];
txtidx = new int[patches.length];
for (int i = 0; i < patches.length; i++) {
PatchDefinition pd = (PatchDefinition) patches[i];
steps[i] = pd.step;
txtidx[i] = pd.getTextureIndex();
}
}
}
// Set block ID and ID+meta groups
updateGroup(GROUP_BLOCKID, "blk" + blk.baseState.globalStateIndex);
updateGroup(GROUP_BLOCKIDMETA, "blk" + blk.globalStateIndex);
// Get materials for patches
String[] mats = shader.getCurrentBlockMaterials(blk, map, txtidx, steps);
if (patches != null) { // Patch based model?
for (int i = 0; i < patches.length; i++) {
addPatch((PatchDefinition) patches[i], map.getX(), map.getY(), map.getZ(), mats[i]);
}
}
else {
boolean opaque = HDBlockStateTextureMap.getTransparency(blk) == BlockTransparency.OPAQUE;
for (int face = 0; face < 6; face++) {
DynmapBlockState blk2 = map.getBlockTypeAt(BlockStep.oppositeValues[face]); // Get block in direction
// If we're not solid, or adjacent block is not solid, draw side
if ((!opaque) || blk2.isAir() || edgebits[face] || (HDBlockStateTextureMap.getTransparency(blk2) != BlockTransparency.OPAQUE)) {
addPatch(defaultPathces[face], map.getX(), map.getY(), map.getZ(), mats[face]);
}
}
}
}
private int[] getTextureUVs(PatchDefinition pd, int rot) {
int[] uv = new int[4];
if (rot == ROT0) {
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
}
else if (rot == ROT90) { // 90 degrees on texture
uv[0] = uvs.getVectorIndex(1.0 - pd.vmin, pd.umin, 0);
uv[1] = uvs.getVectorIndex(1.0 - pd.vmin, pd.umax, 0);
uv[2] = uvs.getVectorIndex(1.0 - pd.vmax, pd.umax, 0);
uv[3] = uvs.getVectorIndex(1.0 - pd.vmax, pd.umin, 0);
}
else if (rot == ROT180) { // 180 degrees on texture
uv[0] = uvs.getVectorIndex(1.0 - pd.umin, 1.0 - pd.vmin, 0);
uv[1] = uvs.getVectorIndex(1.0 - pd.umax, 1.0 - pd.vmin, 0);
uv[2] = uvs.getVectorIndex(1.0 - pd.umax, 1.0 - pd.vmax, 0);
uv[3] = uvs.getVectorIndex(1.0 - pd.umin, 1.0 - pd.vmax, 0);
}
else if (rot == ROT270) { // 270 degrees on texture
uv[0] = uvs.getVectorIndex(pd.vmin, 1.0 - pd.umin, 0);
uv[1] = uvs.getVectorIndex(pd.vmin, 1.0 - pd.umax, 0);
uv[2] = uvs.getVectorIndex(pd.vmax, 1.0 - pd.umax, 0);
uv[3] = uvs.getVectorIndex(pd.vmax, 1.0 - pd.umin, 0);
}
else if (rot == HFLIP) {
uv[0] = uvs.getVectorIndex(1.0 - pd.umin, pd.vmin, 0);
uv[1] = uvs.getVectorIndex(1.0 - pd.umax, pd.vmin, 0);
uv[2] = uvs.getVectorIndex(1.0 - pd.umax, pd.vmax, 0);
uv[3] = uvs.getVectorIndex(1.0 - pd.umin, pd.vmax, 0);
}
else {
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
}
return uv;
}
/**
* Add patch as face to output
*/
private void addPatch(PatchDefinition pd, double x, double y, double z, String material) throws IOException {
// No material? No face
if (material == null) {
return;
}
int rot = 0;
int rotidx = material.indexOf('@'); // Check for rotation modifier
if (rotidx >= 0) {
rot = material.charAt(rotidx+1) - '0'; // 0-3
material = material.substring(0, rotidx);
}
int[] v = new int[4];
int[] uv = getTextureUVs(pd, rot);
// Get offsets for U and V from origin
double ux = pd.xu - pd.x0;
double uy = pd.yu - pd.y0;
double uz = pd.zu - pd.z0;
double vx = pd.xv - pd.x0;
double vy = pd.yv - pd.y0;
double vz = pd.zv - pd.z0;
// Offset to origin corner
x = x + pd.x0;
y = y + pd.y0;
z = z + pd.z0;
// Origin corner, offset by umin, vmin
v[0] = vertices.getVectorIndex(x + ux*pd.umin + vx*pd.vmin, y + uy*pd.umin + vy*pd.vmin, z + uz*pd.umin + vz*pd.vmin);
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
// Second is end of U (umax, vmin)
v[1] = vertices.getVectorIndex(x + ux*pd.umax + vx*pd.vmin, y + uy*pd.umax + vy*pd.vmin, z + uz*pd.umax + vz*pd.vmin);
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
// Third is end of U+V (umax, vmax)
v[2] = vertices.getVectorIndex(x + ux*pd.umax + vx*pd.vmax, y + uy*pd.umax + vy*pd.vmax, z + uz*pd.umax + vz*pd.vmax);
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
// Forth is end of V (umin, vmax)
v[3] = vertices.getVectorIndex(x + ux*pd.umin + vx*pd.vmax, y + uy*pd.umin + vy*pd.vmax, z + uz*pd.umin + vz*pd.vmax);
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
// Add patch to file
addPatchToFile(v, uv, pd.sidevis, material, rot);
}
private void addPatchToFile(int[] v, int[] uv, SideVisible sv, String material, int rot) throws IOException {
List<Face> faces = facesByTexture.get(material);
if (faces == null) {
faces = new ArrayList<Face>();
facesByTexture.put(material, faces);
}
// If needed, rotate the UV sequence
if (rot == HFLIP) { // Flip horizonntal
int newuv[] = new int[uv.length];
for (int i = 0; i < uv.length; i++) {
newuv[i] = uv[i ^ 1];
}
uv = newuv;
}
else if (rot != ROT0) {
int newuv[] = new int[uv.length];
for (int i = 0; i < uv.length; i++) {
newuv[i] = uv[(i+4-rot) % uv.length];
}
uv = newuv;
}
Face f = new Face();
f.groupLine = updateGroup(GROUP_TEXTURE, material);
switch (sv) {
case TOP:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
break;
case TOPFLIP:
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[2], v[2], uv[3], v[1], uv[0], v[0], uv[1]);
break;
case TOPFLIPV:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
break;
case TOPFLIPHV:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
break;
case BOTTOM:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[3], v[2], uv[2], v[1], uv[1], v[0], uv[0]);
break;
case BOTH:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[3], v[2], uv[2], v[1], uv[1], v[0], uv[0]);
break;
case FLIP:
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[2], v[2], uv[3], v[1], uv[0], v[0], uv[1]);
break;
}
faces.add(f);
}
public Set<String> getMaterialIDs() {
return matIDs;
}
private static final boolean getSubblock(short[] mod, int x, int y, int z) {
if ((x >= 0) && (x < MODELSCALE) && (y >= 0) && (y < MODELSCALE) && (z >= 0) && (z < MODELSCALE)) {
return mod[MODELSCALE*MODELSCALE*y + MODELSCALE*z + x] != 0;
}
return false;
}
// Scan along X axis
private int scanX(short[] tmod, int x, int y, int z) {
int xlen = 0;
while (getSubblock(tmod, x+xlen, y, z)) {
xlen++;
}
return xlen;
}
// Scan along Z axis for rows matching given x length
private int scanZ(short[] tmod, int x, int y, int z, int xlen) {
int zlen = 0;
while (scanX(tmod, x, y, z+zlen) >= xlen) {
zlen++;
}
return zlen;
}
// Scan along Y axis for layers matching given X and Z lengths
private int scanY(short[] tmod, int x, int y, int z, int xlen, int zlen) {
int ylen = 0;
while (scanZ(tmod, x, y+ylen, z, xlen) >= zlen) {
ylen++;
}
return ylen;
}
private void addSubblock(short[] tmod, int x, int y, int z, List<RenderPatch> list) {
// Find dimensions of cuboid
int xlen = scanX(tmod, x, y, z);
int zlen = scanZ(tmod, x, y, z, xlen);
int ylen = scanY(tmod, x, y, z, xlen, zlen);
// Add equivalent of boxblock
CustomRenderer.addBox(HDBlockModels.getPatchDefinitionFactory(), list,
BLKSIZE * x, BLKSIZE * (x+xlen),
BLKSIZE * y, BLKSIZE * (y+ylen),
BLKSIZE * z, BLKSIZE * (z+zlen),
HDBlockModels.boxPatchList);
// And remove blocks from model (since we have them covered)
for (int xx = 0; xx < xlen; xx++) {
for (int yy = 0; yy < ylen; yy++) {
for (int zz = 0; zz < zlen; zz++) {
tmod[MODELSCALE*MODELSCALE*(y+yy) + MODELSCALE*(z+zz) + (x+xx)] = 0;
}
}
}
}
private PatchDefinition[] getScaledModelAsPatches(short[] mod) {
ArrayList<RenderPatch> list = new ArrayList<RenderPatch>();
short[] tmod = Arrays.copyOf(mod, mod.length); // Make copy
for (int y = 0; y < MODELSCALE; y++) {
for (int z = 0; z < MODELSCALE; z++) {
for (int x = 0; x < MODELSCALE; x++) {
if (getSubblock(tmod, x, y, z)) { // If occupied, try to add to list
addSubblock(tmod, x, y, z, list);
}
}
}
}
PatchDefinition[] pd = new PatchDefinition[list.size()];
for (int i = 0; i < pd.length; i++) {
pd[i] = (PatchDefinition) list.get(i);
}
return pd;
}
private String updateGroup(int grpIndex, String newgroup) {
if (enabledGroups[grpIndex]) {
if (!newgroup.equals(group[grpIndex])) {
group[grpIndex] = newgroup;
String newline = "g";
for (int i = 0; i < GROUP_COUNT; i++) {
if (enabledGroups[i]) {
newline += " " + group[i];
}
}
newline += "\n";
groupline = newline;
}
}
return groupline;
}
public boolean getGroupEnabled(int grpIndex) {
if (grpIndex < enabledGroups.length) {
return enabledGroups[grpIndex];
}
else {
return false;
}
}
public void setGroupEnabled(int grpIndex, boolean set) {
if (grpIndex < enabledGroups.length) {
enabledGroups[grpIndex] = set;
}
}
public String getBaseName() {
return basename;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,296 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import java.util.BitSet;
import java.util.List;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
public class CaveHDShader implements HDShader {
private String name;
private boolean iflit;
private Color startColor;
private Color endColor;
private BitSet hiddenids = new BitSet();
private void setHidden(DynmapBlockState blk) {
hiddenids.set(blk.globalStateIndex);
}
private void setHidden(String blkname) {
DynmapBlockState bbs = DynmapBlockState.getBaseStateByName(blkname);
if (bbs.isNotAir()) {
for (int i = 0; i < bbs.getStateCount(); i++) {
setHidden(bbs.getState(i));
}
}
}
private boolean isHidden(DynmapBlockState blk) {
return hiddenids.get(blk.globalStateIndex);
}
public CaveHDShader(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
iflit = configuration.getBoolean("onlyiflit", false);
startColor = configuration.getColor("startColor", null);
endColor = configuration.getColor("endColor", null);
for (int i = 0; i < DynmapBlockState.getGlobalIndexMax(); i++) {
DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(i);
if (bs.isAir() || bs.isWater()) {
setHidden(bs);
}
}
List<Object> hidden = configuration.getList("hiddennames");
if(hidden != null) {
for(Object o : hidden) {
if(o instanceof String) {
setHidden((String) o);
}
}
}
else {
setHidden(DynmapBlockState.LOG_BLOCK);
setHidden(DynmapBlockState.LEAVES_BLOCK);
setHidden(DynmapBlockState.GLASS_BLOCK);
setHidden(DynmapBlockState.WOODEN_DOOR_BLOCK);
setHidden(DynmapBlockState.IRON_DOOR_BLOCK);
setHidden(DynmapBlockState.SNOW_BLOCK);
setHidden(DynmapBlockState.ICE_BLOCK);
setHidden(DynmapBlockState.SNOW_LAYER_BLOCK);
for (int i = 0; i < DynmapBlockState.getGlobalIndexMax(); i++) {
DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(i);
if (bs.isLeaves() || bs.isSnow() || bs.isLog()) {
setHidden(bs);
}
}
}
}
@Override
public boolean isBiomeDataNeeded() {
return false;
}
@Override
public boolean isRawBiomeDataNeeded() {
return false;
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return iflit;
}
@Override
public String getName() {
return name;
}
private class OurShaderState implements HDShaderState {
private Color color;
protected MapIterator mapiter;
protected HDMap map;
private boolean air;
private final int sealevel;
private final int ymax, ymin;
final int[] lightingTable;
private OurShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache) {
this.mapiter = mapiter;
this.map = map;
this.color = new Color();
this.ymax = mapiter.getWorldHeight() - 1;
this.ymin = mapiter.getWorldYMin();
this.sealevel = mapiter.getWorldSeaLevel();
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
@Override
public HDShader getShader() {
return CaveHDShader.this;
}
/**
* Get our map
*/
@Override
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
@Override
public HDLighting getLighting() {
return map.getLighting();
}
/**
* Reset renderer state for new ray
*/
@Override
public void reset(HDPerspectiveState ps) {
color.setTransparent();
air = true;
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
@Override
public boolean processBlock(HDPerspectiveState ps) {
DynmapBlockState blocktype = ps.getBlockState();
if (isHidden(blocktype)) {
blocktype = DynmapBlockState.AIR;
}
else if (blocktype.isNotAir()) {
air = false;
return false;
}
if (blocktype.isAir() && !air) {
if(iflit && (ps.getMapIterator().getBlockEmittedLight() == 0)) {
return false;
}
int cr, cg, cb;
int mult;
int y = mapiter.getY();
if((startColor != null) && (endColor != null))
{
double interp = ((double)(y - this.ymin)) / (this.ymax - this.ymin);
cr = (int)(((1.0 - interp) * startColor.getRed()) + (interp * endColor.getRed()));
cg = (int)(((1.0 - interp) * startColor.getGreen()) + (interp * endColor.getGreen()));
cb = (int)(((1.0 - interp) * startColor.getBlue()) + (interp * endColor.getBlue()));
}
else
{
if (y < this.sealevel) {
cr = 0;
cg = 64 + ((192 * (y - this.ymin)) / (this.sealevel - this.ymin));
cb = 255 - (255 * (y - this.ymin)) / (this.sealevel - this.ymin);
} else {
cr = (255 * (y - this.sealevel)) / (this.ymax - this.sealevel);
cg = 255;
cb = 0;
}
}
/* Figure out which color to use */
switch(ps.getLastBlockStep()) {
case X_PLUS:
case X_MINUS:
mult = 224;
break;
case Z_PLUS:
case Z_MINUS:
mult = 256;
break;
default:
mult = 160;
break;
}
cr = cr * mult / 256;
cg = cg * mult / 256;
cb = cb * mult / 256;
color.setRGBA(cr, cg, cb, 255);
return true;
}
return false;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
@Override
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
@Override
public void getRayColor(Color c, int index) {
c.setColor(color);
}
/**
* Clean up state object - called after last ray completed
*/
@Override
public void cleanup() {
}
@Override
public DynLongHashMap getCTMTextureCache() {
return null;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of perspective
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new OurShaderState(mapiter, map, cache);
}
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
throw new IOException("Export unsupported");
}
private static final String[] nulllist = new String[0];
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps) {
return nulllist;
}
}

View file

@ -0,0 +1,231 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
// Shader for color coding by chunk data version
public class ChunkStatusHDShader implements HDShader {
private final String name;
private static class ChunkStatusMap {
Color defcolor;
ChunkStatusMap(String s, int c) {
defcolor = new Color((c>>16)&0xFF, (c>>8)&0xFF, c&0xFF);
statusmap.put(s, this);
}
};
private static HashMap<String, ChunkStatusMap> statusmap = new HashMap<String, ChunkStatusMap>();
static {
new ChunkStatusMap("empty", 0xFF0000);
new ChunkStatusMap("structure_starts", 0xFF1493);
new ChunkStatusMap("structure_references", 0xFF7F50);
new ChunkStatusMap("biomes", 0xFFA500);
new ChunkStatusMap("noise", 0xFFD700);
new ChunkStatusMap("surface", 0xFFFF00);
new ChunkStatusMap("carvers", 0xFFEFD5);
new ChunkStatusMap("liquid_carvers", 0xF0E68C);
new ChunkStatusMap("features", 0xBDB76B);
new ChunkStatusMap("initialize_light", 0xAAA0AA);
new ChunkStatusMap("light", 0xDDA0DD);
new ChunkStatusMap("heightmaps", 0x9370DB);
new ChunkStatusMap("spawn", 0xFF00FF);
new ChunkStatusMap("full", 0x32CD32);
}
final static Color unknown_color = new Color(255, 255, 255);
private ArrayList<String> unknown_state = new ArrayList<String>();
public ChunkStatusHDShader(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
}
@Override
public boolean isBiomeDataNeeded() {
return false;
}
@Override
public boolean isRawBiomeDataNeeded() {
return false;
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return false;
}
@Override
public String getName() {
return name;
}
private class OurShaderState implements HDShaderState {
private Color color[];
private Color c;
protected HDMap map;
private HDLighting lighting;
final int[] lightingTable;
private OurShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
this.map = map;
this.lighting = map.getLighting();
if(lighting.isNightAndDayEnabled()) {
color = new Color[] { new Color(), new Color() };
}
else {
color = new Color[] { new Color() };
}
c = new Color();
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
public HDShader getShader() {
return ChunkStatusHDShader.this;
}
/**
* Get our map
*/
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
public HDLighting getLighting() {
return lighting;
}
/**
* Reset renderer state for new ray
*/
public void reset(HDPerspectiveState ps) {
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
if (ps.getBlockState().isAir()) {
return false;
}
String cs = ps.getMapIterator().getChunkStatus(); // Get data version
ChunkStatusMap csm = statusmap.get(cs);
if (csm != null) {
c.setColor(csm.defcolor);
}
else {
c.setColor(unknown_color);
if (!unknown_state.contains(cs)) {
Log.warning("Unknown chunk status: " + cs);
unknown_state.add(cs);
}
}
/* Handle light level, if needed */
lighting.applyLighting(ps, this, c, color);
return true;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
public void getRayColor(Color c, int index) {
c.setColor(color[index]);
}
/**
* Clean up state object - called after last ray completed
*/
public void cleanup() {
}
@Override
public DynLongHashMap getCTMTextureCache() {
return null;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of perspecitve
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new OurShaderState(mapiter, map, cache, scale);
}
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
throw new IOException("Export unsupported");
}
private static final String[] nulllist = new String[0];
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps) {
return nulllist;
}
}

View file

@ -0,0 +1,258 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import java.util.ArrayList;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
// Shader for color coding by chunk data version
public class ChunkVersionHDShader implements HDShader {
private final String name;
private static class DataVersionMap {
int dataVersion;
//String version;
Color defcolor;
DataVersionMap(int dv, String v, int c) {
dataVersion = dv;
//version = v;
defcolor = new Color((c>>16)&0xFF, (c>>8)&0xFF, c&0xFF);
}
};
// Mapping from https://minecraft.wiki/w/Data_version
final static DataVersionMap[] versionmap = {
new DataVersionMap(0, "unknown", 0x202020),
new DataVersionMap(1519, "1.13.0", 0xF9E79F),
new DataVersionMap(1628, "1.13.1", 0xF4D03F),
new DataVersionMap(1631, "1.13.2", 0xD4AC0D),
new DataVersionMap(1952, "1.14.0", 0xABEBC6),
new DataVersionMap(1957, "1.14.1", 0x58D68D),
new DataVersionMap(1963, "1.14.2", 0x28B463),
new DataVersionMap(1968, "1.14.3", 0x239B56),
new DataVersionMap(1976, "1.14.4", 0x1D8348),
new DataVersionMap(2225, "1.15.0", 0xAED6F1),
new DataVersionMap(2227, "1.15.1", 0x5DADE2),
new DataVersionMap(2230, "1.15.2", 0x2E86C1),
new DataVersionMap(2566, "1.16.0", 0xD7BDE2),
new DataVersionMap(2567, "1.16.1", 0xC39BD3),
new DataVersionMap(2578, "1.16.2", 0xAF7AC5),
new DataVersionMap(2580, "1.16.3", 0x9B59B6),
new DataVersionMap(2584, "1.16.4", 0x884EA0),
new DataVersionMap(2586, "1.16.5", 0x76448A),
new DataVersionMap(2724, "1.17.0", 0xF5CBA7),
new DataVersionMap(2730, "1.17.1", 0xEB984E),
new DataVersionMap(2860, "1.18.0", 0xA3E4D7),
new DataVersionMap(2865, "1.18.1", 0x48C9B0),
new DataVersionMap(2975, "1.18.2", 0x38bfa5),
new DataVersionMap(3105, "1.19", 0xd56f82),
new DataVersionMap(3116, "1.19.1", 0xe196a4),
new DataVersionMap(3120, "1.19.2", 0xe7aeb8),
new DataVersionMap(3218, "1.19.3", 0xf8c0c8),
new DataVersionMap(3337, "1.19.4", 0xffb6c1),
new DataVersionMap(3465, "1.20.1", 0xe7aeb10),
new DataVersionMap(3578, "1.20.2", 0xe196a7),
new DataVersionMap(3698, "1.20.3", 0xe7aeb11),
new DataVersionMap(3700, "1.20.4", 0xe196a8),
new DataVersionMap(3837, "1.20.5", 0xe7aeb12),
new DataVersionMap(3839, "1.20.6", 0xe196a9),
new DataVersionMap(3953, "1.21.0", 0xe7aeb13),
};
final static Color unknown_color = new Color(255, 255, 255);
private ArrayList<Integer> unknown_vers = new ArrayList<Integer>();
public ChunkVersionHDShader(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
}
@Override
public boolean isBiomeDataNeeded() {
return false;
}
@Override
public boolean isRawBiomeDataNeeded() {
return false;
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return false;
}
@Override
public String getName() {
return name;
}
private class OurShaderState implements HDShaderState {
private Color color[];
private Color c;
protected HDMap map;
private HDLighting lighting;
final int[] lightingTable;
private OurShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
this.map = map;
this.lighting = map.getLighting();
if(lighting.isNightAndDayEnabled()) {
color = new Color[] { new Color(), new Color() };
}
else {
color = new Color[] { new Color() };
}
c = new Color();
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
public HDShader getShader() {
return ChunkVersionHDShader.this;
}
/**
* Get our map
*/
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
public HDLighting getLighting() {
return lighting;
}
/**
* Reset renderer state for new ray
*/
public void reset(HDPerspectiveState ps) {
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
if (ps.getBlockState().isAir()) {
return false;
}
int ver = ps.getMapIterator().getDataVersion(); // Get data version
boolean match = false;
// Find last record <= version
for (int i = 0; i < versionmap.length; i++) {
if (ver <= versionmap[i].dataVersion) {
c.setColor(versionmap[i].defcolor);
match = true;
break;
}
}
if (!match) {
c.setColor(unknown_color);
if (!unknown_vers.contains(ver)) {
Log.warning("Unknown chunk dataVersion: " + ver);
unknown_vers.add(ver);
}
}
/* Handle light level, if needed */
lighting.applyLighting(ps, this, c, color);
return true;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
public void getRayColor(Color c, int index) {
c.setColor(color[index]);
}
/**
* Clean up state object - called after last ray completed
*/
public void cleanup() {
}
@Override
public DynLongHashMap getCTMTextureCache() {
return null;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of perspecitve
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new OurShaderState(mapiter, map, cache, scale);
}
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
throw new IOException("Export unsupported");
}
private static final String[] nulllist = new String[0];
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps) {
return nulllist;
}
}

View file

@ -0,0 +1,62 @@
package org.dynmap.hdmap;
import java.util.BitSet;
import java.util.Map;
import org.dynmap.Log;
import org.dynmap.renderer.CustomRenderer;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.renderer.MapDataContext;
import org.dynmap.renderer.RenderPatch;
public class CustomBlockModel extends HDBlockModel {
public CustomRenderer render;
public CustomBlockModel(DynmapBlockState bstate, BitSet databits, String classname, Map<String,String> classparm, String blockset) {
super(bstate, databits, blockset);
try {
Class<?> cls = Class.forName(classname); /* Get class */
render = (CustomRenderer) cls.getDeclaredConstructor().newInstance();
if(render.initializeRenderer(HDBlockModels.pdf, bstate.blockName, databits, classparm) == false) {
Log.severe("Error loading custom renderer - " + classname);
render = null;
}
else {
if(render.getTileEntityFieldsNeeded() != null) {
DynmapBlockState bbs = bstate.baseState;
for(int i = 0; i < bbs.getStateCount(); i++) {
if (databits.isEmpty() || databits.get(i)) {
DynmapBlockState bs = bbs.getState(i);
HDBlockModels.customModelsRequestingTileData.set(bs.globalStateIndex);
}
}
}
}
} catch (Exception x) {
Log.severe("Error loading custom renderer - " + classname, x);
render = null;
}
}
@Override
public int getTextureCount() {
return render.getMaximumTextureCount(HDBlockModels.pdf);
}
public boolean isOnlyBlockStateSensitive() {
return render.isOnlyBlockStateSensitive();
}
private static final RenderPatch[] empty_list = new RenderPatch[0];
public RenderPatch[] getMeshForBlock(MapDataContext ctx) {
if(render != null)
return render.getRenderPatchList(ctx);
else
return empty_list;
}
@Override
public void removed(DynmapBlockState blk) {
super.removed(blk);
HDBlockModels.customModelsRequestingTileData.clear(blk.globalStateIndex);
}
}

View file

@ -0,0 +1,90 @@
package org.dynmap.hdmap;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.json.simple.JSONObject;
import static org.dynmap.JSONUtils.s;
public class DefaultHDLighting implements HDLighting {
private String name;
protected boolean grayscale;
protected boolean blackandwhite;
protected int blackthreshold;
protected final Color graytone;
protected final Color graytonedark;
public DefaultHDLighting(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
grayscale = configuration.getBoolean("grayscale", false);
graytone = configuration.getColor("graytone", "#FFFFFF");
graytonedark = configuration.getColor("graytonedark", "#000000");
blackandwhite = configuration.getBoolean("blackandwhite", false);
if (blackandwhite) grayscale = false;
blackthreshold = configuration.getInteger("blackthreshold", 0x40);
}
protected void checkGrayscale(Color[] outcolor) {
if (grayscale) {
for (int i = 0; i < outcolor.length; i++) {
outcolor[i].setGrayscale();
outcolor[i].scaleColor(graytonedark,graytone);
}
}
else if (blackandwhite) {
for (int i = 0; i < outcolor.length; i++) {
outcolor[i].setGrayscale();
if (outcolor[i].getRed() > blackthreshold) {
outcolor[i].setColor(graytone);
}
else {
outcolor[i].setColor(graytonedark);
}
}
}
}
/* Get lighting name */
public String getName() { return name; }
/* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */
public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) {
for(int i = 0; i < outcolor.length; i++)
outcolor[i].setColor(incolor);
checkGrayscale(outcolor);
}
/* Test if Biome Data is needed for this renderer */
public boolean isBiomeDataNeeded() { return false; }
/* Test if raw biome temperature/rainfall data is needed */
public boolean isRawBiomeDataNeeded() { return false; }
/* Test if highest block Y data is needed */
public boolean isHightestBlockYDataNeeded() { return false; }
/* Tet if block type data needed */
public boolean isBlockTypeDataNeeded() { return false; }
/* Test if night/day is enabled for this renderer */
public boolean isNightAndDayEnabled() { return false; }
/* Test if sky light level needed */
public boolean isSkyLightLevelNeeded() { return false; }
/* Test if emitted light level needed */
public boolean isEmittedLightLevelNeeded() { return false; }
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "lighting", name);
s(mapObject, "nightandday", isNightAndDayEnabled());
}
@Override
public int[] getBrightnessTable(DynmapWorld world) {
return null;
}
}

View file

@ -0,0 +1,320 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import org.dynmap.Color;
import org.dynmap.ColorScheme;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.MapManager;
import org.dynmap.common.BiomeMap;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
public class DefaultHDShader implements HDShader {
private String name;
protected ColorScheme colorScheme;
protected boolean transparency; /* Is transparency support active? */
public enum BiomeColorOption {
NONE, BIOME, TEMPERATURE, RAINFALL
}
protected BiomeColorOption biomecolored = BiomeColorOption.NONE; /* Use biome for coloring */
public DefaultHDShader(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
colorScheme = ColorScheme.getScheme(core, configuration.getString("colorscheme", "default"));
transparency = configuration.getBoolean("transparency", true); /* Default on */
String biomeopt = configuration.getString("biomecolored", "none");
if(biomeopt.equals("biome")) {
biomecolored = BiomeColorOption.BIOME;
}
else if(biomeopt.equals("temperature")) {
biomecolored = BiomeColorOption.TEMPERATURE;
}
else if(biomeopt.equals("rainfall")) {
biomecolored = BiomeColorOption.RAINFALL;
}
else {
biomecolored = BiomeColorOption.NONE;
}
}
@Override
public boolean isBiomeDataNeeded() {
return biomecolored == BiomeColorOption.BIOME;
}
@Override
public boolean isRawBiomeDataNeeded() {
return (biomecolored == BiomeColorOption.RAINFALL) || (biomecolored == BiomeColorOption.TEMPERATURE);
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return false;
}
@Override
public String getName() {
return name;
}
private class OurShaderState implements HDShaderState {
private Color color[];
protected MapIterator mapiter;
protected HDMap map;
private Color tmpcolor[];
private int pixelodd;
private HDLighting lighting;
final int[] lightingTable;
private OurShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache) {
this.mapiter = mapiter;
this.map = map;
this.lighting = map.getLighting();
if(lighting.isNightAndDayEnabled()) {
color = new Color[] { new Color(), new Color() };
tmpcolor = new Color[] { new Color(), new Color() };
}
else {
color = new Color[] { new Color() };
tmpcolor = new Color[] { new Color() };
}
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
public HDShader getShader() {
return DefaultHDShader.this;
}
/**
* Get our map
*/
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
public HDLighting getLighting() {
return lighting;
}
/**
* Reset renderer state for new ray
*/
public void reset(HDPerspectiveState ps) {
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
pixelodd = (ps.getPixelX() & 0x3) + (ps.getPixelY()<<1);
}
protected Color[] getBlockColors(DynmapBlockState block) {
int idx = block.globalStateIndex;
if (colorScheme.colors.length > idx) {
return colorScheme.colors[idx];
}
return null;
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
int i;
DynmapBlockState blocktype = ps.getBlockState();
if (blocktype.isAir())
return false;
Color[] colors = getBlockColors(blocktype);
if (colors != null) {
int seq;
int subalpha = ps.getSubmodelAlpha();
/* Figure out which color to use */
switch(ps.getLastBlockStep()) {
case X_PLUS:
case X_MINUS:
seq = 2;
break;
case Z_PLUS:
case Z_MINUS:
seq = 0;
break;
default:
//if(subalpha >= 0) /* We hit a block in a model */
// seq = 4; /* Use smooth top */
//else
if(((pixelodd + mapiter.getY()) & 0x03) == 0)
seq = 3;
else
seq = 1;
break;
}
Color c = colors[seq];
if (c.getAlpha() > 0) {
/* Handle light level, if needed */
lighting.applyLighting(ps, this, c, tmpcolor);
/* If we got alpha from subblock model, use it instead */
if(subalpha >= 0) {
for(int j = 0; j < tmpcolor.length; j++)
tmpcolor[j].setAlpha(Math.max(subalpha,tmpcolor[j].getAlpha()));
}
/* Blend color with accumulated color (weighted by alpha) */
if(!transparency) { /* No transparency support */
for(i = 0; i < color.length; i++)
color[i].setARGB(tmpcolor[i].getARGB() | 0xFF000000);
return true; /* We're done */
}
/* If no previous color contribution, use new color */
else if(color[0].isTransparent()) {
for(i = 0; i < color.length; i++)
color[i].setColor(tmpcolor[i]);
return (color[0].getAlpha() == 255);
}
/* Else, blend and generate new alpha */
else {
int alpha = color[0].getAlpha();
int alpha2 = tmpcolor[0].getAlpha() * (255-alpha) / 255;
int talpha = alpha + alpha2;
for(i = 0; i < color.length; i++)
color[i].setRGBA((tmpcolor[i].getRed()*alpha2 + color[i].getRed()*alpha) / talpha,
(tmpcolor[i].getGreen()*alpha2 + color[i].getGreen()*alpha) / talpha,
(tmpcolor[i].getBlue()*alpha2 + color[i].getBlue()*alpha) / talpha, talpha);
return (talpha >= 254); /* If only one short, no meaningful contribution left */
}
}
}
return false;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
public void getRayColor(Color c, int index) {
c.setColor(color[index]);
}
/**
* Clean up state object - called after last ray completed
*/
public void cleanup() {
}
@Override
public DynLongHashMap getCTMTextureCache() {
return null;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
}
}
private class OurBiomeShaderState extends OurShaderState {
private OurBiomeShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache) {
super(mapiter, map, cache);
}
@Override
protected Color[] getBlockColors(DynmapBlockState blk) {
BiomeMap bio = mapiter.getBiome();
if(bio != null)
return colorScheme.biomecolors[bio.ordinal()];
return null;
}
}
private class OurBiomeRainfallShaderState extends OurShaderState {
private OurBiomeRainfallShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache) {
super(mapiter, map, cache);
}
@Override
protected Color[] getBlockColors(DynmapBlockState blk) {
return colorScheme.getRainColor(mapiter.getBiome().getRainfall());
}
}
private class OurBiomeTempShaderState extends OurShaderState {
private OurBiomeTempShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache) {
super(mapiter, map, cache);
}
@Override
protected Color[] getBlockColors(DynmapBlockState blk) {
return colorScheme.getTempColor(mapiter.getBiome().getTemperature());
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of the perspecitve
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
switch(biomecolored) {
case NONE:
return new OurShaderState(mapiter, map, cache);
case BIOME:
return new OurBiomeShaderState(mapiter, map, cache);
case RAINFALL:
return new OurBiomeRainfallShaderState(mapiter, map, cache);
case TEMPERATURE:
return new OurBiomeTempShaderState(mapiter, map, cache);
}
return null;
}
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
throw new IOException("Export unsupported");
}
private static final String[] nulllist = new String[0];
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx,
BlockStep[] steps) {
return nulllist;
}
}

View file

@ -0,0 +1,39 @@
package org.dynmap.hdmap;
import java.util.BitSet;
import org.dynmap.renderer.DynmapBlockState;
public abstract class HDBlockModel {
private String blockset;
/**
* Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west)
* @param bstate - block state
* @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match)
* @param blockset - ID of block definition set
*/
protected HDBlockModel(DynmapBlockState bstate, BitSet databits, String blockset) {
this.blockset = blockset;
DynmapBlockState bblk = bstate.baseState;
if (bblk.isNotAir()) {
for (int i = 0; i < bblk.getStateCount(); i++) {
if (databits.isEmpty() || databits.get(i)) {
DynmapBlockState bs = bblk.getState(i);
HDBlockModel prev = HDBlockModels.models_by_id_data[bs.globalStateIndex];
HDBlockModels.models_by_id_data[bs.globalStateIndex] = this;
if((prev != null) && (prev != this)) {
prev.removed(bs);
}
}
}
}
}
public String getBlockSet() {
return blockset;
}
public abstract int getTextureCount();
public void removed(DynmapBlockState blk) {
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
package org.dynmap.hdmap;
import java.util.BitSet;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.PatchDefinition;
public class HDBlockPatchModel extends HDBlockModel {
/* Patch model specific attributes */
private PatchDefinition[] patches;
private final int max_texture;
/**
* Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west)
* (for patch models)
* @param bs - block state
* @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match)
* @param patches - list of patches (surfaces composing model)
* @param blockset - ID of set of blocks defining model
*/
public HDBlockPatchModel(DynmapBlockState bs, BitSet databits, PatchDefinition[] patches, String blockset) {
super(bs, databits, blockset);
this.patches = patches;
int max = 0;
for(int i = 0; i < patches.length; i++) {
if((patches[i] != null) && (patches[i].textureindex > max))
max = patches[i].textureindex;
}
this.max_texture = max + 1;
}
/**
* Get patches for block model (if patch model)
* @return patches for model
*/
public final PatchDefinition[] getPatches() {
return patches;
}
/**
* Set patches for block
*/
public final void setPatches(PatchDefinition[] p) {
patches = p;
}
@Override
public int getTextureCount() {
return max_texture;
}
}

View file

@ -0,0 +1,187 @@
package org.dynmap.hdmap;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Map;
import org.dynmap.Log;
import org.dynmap.hdmap.TexturePack.BlockTransparency;
import org.dynmap.hdmap.TexturePack.ColorizingData;
import org.dynmap.renderer.CustomColorMultiplier;
import org.dynmap.renderer.DynmapBlockState;
public class HDBlockStateTextureMap {
private static HDBlockStateTextureMap[] texmaps = new HDBlockStateTextureMap[DynmapBlockState.getGlobalIndexMax()]; // List of texture maps, indexed by global state index
int faces[]; /* texture index of image for each face (indexed by BlockStep.ordinal() OR patch index) */
final byte[] layers; /* If layered, each index corresponds to faces index, and value is index of next layer */
final private String blockset;
final int colorMult;
final CustomColorMultiplier custColorMult;
final boolean stdrotate; // Marked for corrected to proper : stdrot=true
final private Integer colorMapping; // If non-null, color mapping texture
final BlockTransparency trans;
public static final HDBlockStateTextureMap BLANK = new HDBlockStateTextureMap();
// Default to a blank mapping
HDBlockStateTextureMap() {
blockset = null;
colorMult = 0;
custColorMult = null;
faces = new int[] { TexturePack.TILEINDEX_BLANK, TexturePack.TILEINDEX_BLANK, TexturePack.TILEINDEX_BLANK, TexturePack.TILEINDEX_BLANK, TexturePack.TILEINDEX_BLANK, TexturePack.TILEINDEX_BLANK };
layers = null;
stdrotate = true;
colorMapping = null;
trans = BlockTransparency.TRANSPARENT;
}
// Create block state map with given attributes
public HDBlockStateTextureMap(int[] faces, byte[] layers, int colorMult, CustomColorMultiplier custColorMult, String blockset, boolean stdrot, Integer colorIndex, BlockTransparency trans) {
this.faces = faces;
this.layers = layers;
this.colorMult = colorMult;
this.custColorMult = custColorMult;
this.blockset = blockset;
this.stdrotate = stdrot;
this.colorMapping = colorIndex;
this.trans = trans;
}
// Shallow copy state from another state map
public HDBlockStateTextureMap(HDBlockStateTextureMap map, BlockTransparency bt) {
this.faces = map.faces;
this.layers = map.layers;
this.blockset = map.blockset;
this.colorMult = map.colorMult;
this.custColorMult = map.custColorMult;
this.stdrotate = map.stdrotate;
this.colorMapping = map.colorMapping;
if (bt != null)
this.trans = bt;
else
this.trans = map.trans;
}
// Get texture index for given face
public int getIndexForFace(int face) {
if ((faces != null) && (faces.length > face))
return faces[face];
return TexturePack.TILEINDEX_BLANK;
}
public void resizeFaces(int cnt) {
int[] newfaces = new int[cnt];
System.arraycopy(faces, 0, newfaces, 0, faces.length);
for(int i = faces.length; i < cnt; i++) {
newfaces[i] = TexturePack.TILEINDEX_BLANK;
}
faces = newfaces;
}
// Add block state to table, with given block IDs and state indexes
public void addToTable(Map<DynmapBlockState, BitSet> states, int lineNum) {
/* Add entries to lookup table */
for (DynmapBlockState baseblk : states.keySet()) {
if (baseblk.isNotAir()) {
BitSet stateidx = states.get(baseblk);
for (int stateid = stateidx.nextSetBit(0); stateid >= 0; stateid = stateidx.nextSetBit(stateid+1)) {
DynmapBlockState bs = baseblk.getState(stateid);
if (bs.isAir()) {
Log.warning("Invalid texture block state: " + baseblk.blockName + ":" + stateid);
continue;
}
if ((this.blockset != null) && (this.blockset.equals("core") == false)) {
HDBlockModels.resetIfNotBlockSet(bs, this.blockset);
}
copyToStateIndex(bs, this, null);
}
}
else {
Log.warning("Invalid texture block name: " + baseblk.blockName + ((lineNum > 0) ? "(line " + lineNum + ")" : ""));
}
}
}
private static final void resize(int newend) {
if (newend < texmaps.length) return;
HDBlockStateTextureMap[] newm = new HDBlockStateTextureMap[newend+1];
System.arraycopy(texmaps, 0, newm, 0, texmaps.length);
Arrays.fill(newm, texmaps.length, newm.length, HDBlockStateTextureMap.BLANK);
texmaps = newm;
}
// Initialize/reset block texture table
public static void initializeTable() {
Arrays.fill(texmaps, HDBlockStateTextureMap.BLANK);
}
// Lookup records by block state
public static final HDBlockStateTextureMap getByBlockState(DynmapBlockState blk) {
HDBlockStateTextureMap m = HDBlockStateTextureMap.BLANK;
try {
m = texmaps[blk.globalStateIndex];
} catch (ArrayIndexOutOfBoundsException x) {
resize(blk.globalStateIndex);
}
return m;
}
// Copy given block state to given state index
public static void copyToStateIndex(DynmapBlockState blk, HDBlockStateTextureMap map, TexturePack.BlockTransparency trans) {
resize(blk.globalStateIndex);
if (trans == null) {
trans = map.trans;
}
// Force waterloogged blocks to use SEMITRANSPARENT (same as water)
if ((trans == TexturePack.BlockTransparency.TRANSPARENT) && blk.isWaterlogged()) {
trans = TexturePack.BlockTransparency.SEMITRANSPARENT;
}
texmaps[blk.globalStateIndex] = new HDBlockStateTextureMap(map, trans);
}
// Copy textures from source block ID to destination
public static void remapTexture(String dest, String src) {
DynmapBlockState dblk = DynmapBlockState.getBaseStateByName(dest);
DynmapBlockState sblk = DynmapBlockState.getBaseStateByName(src);
int scnt = sblk.getStateCount();
for (int i = 0; i < dblk.getStateCount(); i++) {
int didx = dblk.getState(i).globalStateIndex;
int sidx = sblk.getState(i % scnt).globalStateIndex;
texmaps[didx] = new HDBlockStateTextureMap(texmaps[sidx], null);
}
}
// Get by global state index
public static HDBlockStateTextureMap getByGlobalIndex(int gidx) {
HDBlockStateTextureMap m = HDBlockStateTextureMap.BLANK;
try {
m = texmaps[gidx];
} catch (ArrayIndexOutOfBoundsException x) {
resize(gidx);
}
return m;
}
// Get state by index
public final HDBlockStateTextureMap getStateMap(DynmapBlockState blk, int stateid) {
return getByGlobalIndex(blk.getState(stateid).globalStateIndex);
}
// Get transparency for given block ID
public static BlockTransparency getTransparency(DynmapBlockState blk) {
BlockTransparency trans = BlockTransparency.OPAQUE;
try {
trans = texmaps[blk.globalStateIndex].trans;
} catch (ArrayIndexOutOfBoundsException x) {
resize(blk.globalStateIndex);
}
return trans;
}
// Build copy of block colorization data
public static ColorizingData getColorizingData() {
ColorizingData map = new ColorizingData();
for (int j = 0; j < texmaps.length; j++) {
if (texmaps[j].colorMapping != null) {
map.setBlkStateValue(DynmapBlockState.getStateByGlobalIndex(j), texmaps[j].colorMapping);
}
}
return map;
}
}

View file

@ -0,0 +1,191 @@
package org.dynmap.hdmap;
import java.util.BitSet;
import java.util.HashMap;
import org.dynmap.renderer.DynmapBlockState;
public class HDBlockVolumetricModel extends HDBlockModel {
/* Volumetric model specific attributes */
private long blockflags[];
private int nativeres;
private HashMap<Integer, short[]> scaledblocks;
/**
* Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west)
* (for volumetric models)
* @param bs - block state
* @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match)
* @param nativeres - native subblocks per edge of cube (up to 64)
* @param blockflags - array of native^2 long integers representing volume of block (bit X of element (nativeres*Y+Z) is set if that subblock is filled)
* if array is short, other elements area are assumed to be zero (fills from bottom of block up)
* @param blockset - ID of set of blocks defining model
*/
public HDBlockVolumetricModel(DynmapBlockState bs, BitSet databits, int nativeres, long[] blockflags, String blockset) {
super(bs, databits, blockset);
this.nativeres = nativeres;
this.blockflags = new long[nativeres * nativeres];
System.arraycopy(blockflags, 0, this.blockflags, 0, blockflags.length);
}
/**
* Test if given native block is filled (for volumetric model)
*
* @param x - X coordinate
* @param y - Y coordinate
* @param z - Z coordinate
* @return true if set, false if not
*/
public final boolean isSubblockSet(int x, int y, int z) {
return ((blockflags[nativeres*y+z] & (1 << x)) != 0);
}
/**
* Set subblock value (for volumetric model)
*
* @param x - X coordinate
* @param y - Y coordinate
* @param z - Z coordinate
* @param isset - true = set, false = clear
*/
public final void setSubblock(int x, int y, int z, boolean isset) {
if(isset)
blockflags[nativeres*y+z] |= (1 << x);
else
blockflags[nativeres*y+z] &= ~(1 << x);
}
/**
* Get scaled map of block: will return array of alpha levels, corresponding to how much of the
* scaled subblocks are occupied by the original blocks (indexed by Y*res*res + Z*res + X)
* @param res - requested scale (res subblocks per edge of block)
* @return array of alpha values (0-255), corresponding to resXresXres subcubes of block
*/
public short[] getScaledMap(int res) {
if(scaledblocks == null) { scaledblocks = new HashMap<Integer, short[]>(); }
short[] map = scaledblocks.get(Integer.valueOf(res));
if(map == null) {
map = new short[res*res*res];
if(res == nativeres) {
for(int i = 0; i < blockflags.length; i++) {
for(int j = 0; j < nativeres; j++) {
if((blockflags[i] & (1 << j)) != 0)
map[res*i+j] = 255;
}
}
}
/* If scaling from smaller sub-blocks to larger, each subblock contributes to 1-2 blocks
* on each axis: need to calculate crossovers for each, and iterate through smaller
* blocks to accumulate contributions
*/
else if(res > nativeres) {
int weights[] = new int[res];
int offsets[] = new int[res];
/* LCM of resolutions is used as length of line (res * nativeres)
* Each native block is (res) long, each scaled block is (nativeres) long
* Each scaled block overlaps 1 or 2 native blocks: starting with native block 'offsets[]' with
* 'weights[]' of its (res) width in the first, and the rest in the second
*/
for(int v = 0, idx = 0; v < res*nativeres; v += nativeres, idx++) {
offsets[idx] = (v/res); /* Get index of the first native block we draw from */
if((v+nativeres-1)/res == offsets[idx]) { /* If scaled block ends in same native block */
weights[idx] = nativeres;
}
else { /* Else, see how much is in first one */
weights[idx] = (offsets[idx] + res) - v;
weights[idx] = (offsets[idx]*res + res) - v;
}
}
/* Now, use weights and indices to fill in scaled map */
for(int y = 0, off = 0; y < res; y++) {
int ind_y = offsets[y];
int wgt_y = weights[y];
for(int z = 0; z < res; z++) {
int ind_z = offsets[z];
int wgt_z = weights[z];
for(int x = 0; x < res; x++, off++) {
int ind_x = offsets[x];
int wgt_x = weights[x];
int raw_w = 0;
for(int xx = 0; xx < 2; xx++) {
int wx = (xx==0)?wgt_x:(nativeres-wgt_x);
if(wx == 0) continue;
for(int yy = 0; yy < 2; yy++) {
int wy = (yy==0)?wgt_y:(nativeres-wgt_y);
if(wy == 0) continue;
for(int zz = 0; zz < 2; zz++) {
int wz = (zz==0)?wgt_z:(nativeres-wgt_z);
if(wz == 0) continue;
if(isSubblockSet(ind_x+xx, ind_y+yy, ind_z+zz)) {
raw_w += wx*wy*wz;
}
}
}
}
map[off] = (short)((255*raw_w) / (nativeres*nativeres*nativeres));
if(map[off] > 255) map[off] = 255;
if(map[off] < 0) map[off] = 0;
}
}
}
}
else { /* nativeres > res */
int weights[] = new int[nativeres];
int offsets[] = new int[nativeres];
/* LCM of resolutions is used as length of line (res * nativeres)
* Each native block is (res) long, each scaled block is (nativeres) long
* Each native block overlaps 1 or 2 scaled blocks: starting with scaled block 'offsets[]' with
* 'weights[]' of its (res) width in the first, and the rest in the second
*/
for(int v = 0, idx = 0; v < res*nativeres; v += res, idx++) {
offsets[idx] = (v/nativeres); /* Get index of the first scaled block we draw to */
if((v+res-1)/nativeres == offsets[idx]) { /* If native block ends in same scaled block */
weights[idx] = res;
}
else { /* Else, see how much is in first one */
weights[idx] = (offsets[idx]*nativeres + nativeres) - v;
}
}
/* Now, use weights and indices to fill in scaled map */
long accum[] = new long[map.length];
for(int y = 0; y < nativeres; y++) {
int ind_y = offsets[y];
int wgt_y = weights[y];
for(int z = 0; z < nativeres; z++) {
int ind_z = offsets[z];
int wgt_z = weights[z];
for(int x = 0; x < nativeres; x++) {
if(isSubblockSet(x, y, z)) {
int ind_x = offsets[x];
int wgt_x = weights[x];
for(int xx = 0; xx < 2; xx++) {
int wx = (xx==0)?wgt_x:(res-wgt_x);
if(wx == 0) continue;
for(int yy = 0; yy < 2; yy++) {
int wy = (yy==0)?wgt_y:(res-wgt_y);
if(wy == 0) continue;
for(int zz = 0; zz < 2; zz++) {
int wz = (zz==0)?wgt_z:(res-wgt_z);
if(wz == 0) continue;
accum[(ind_y+yy)*res*res + (ind_z+zz)*res + (ind_x+xx)] +=
wx*wy*wz;
}
}
}
}
}
}
}
for(int i = 0; i < map.length; i++) {
map[i] = (short)(accum[i]*255/nativeres/nativeres/nativeres);
if(map[i] > 255) map[i] = 255;
if(map[i] < 0) map[i] = 0;
}
}
scaledblocks.put(Integer.valueOf(res), map);
}
return map;
}
@Override
public int getTextureCount() {
return 6;
}
}

View file

@ -0,0 +1,30 @@
package org.dynmap.hdmap;
import org.dynmap.Color;
import org.dynmap.DynmapWorld;
import org.json.simple.JSONObject;
public interface HDLighting {
/* Get lighting name */
String getName();
/* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */
void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor);
/* Test if Biome Data is needed for this renderer */
boolean isBiomeDataNeeded();
/* Test if raw biome temperature/rainfall data is needed */
boolean isRawBiomeDataNeeded();
/* Test if highest block Y data is needed */
boolean isHightestBlockYDataNeeded();
/* Tet if block type data needed */
boolean isBlockTypeDataNeeded();
/* Test if night/day is enabled for this renderer */
boolean isNightAndDayEnabled();
/* Test if sky light level needed */
boolean isSkyLightLevelNeeded();
/* Test if emitted light level needed */
boolean isEmittedLightLevelNeeded();
/* Add shader's contributions to JSON for map object */
void addClientConfiguration(JSONObject mapObject);
/* Get brightness table for given world */
int[] getBrightnessTable(DynmapWorld world);
}

View file

@ -0,0 +1,483 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.a;
import static org.dynmap.JSONUtils.s;
import java.util.ArrayList;
import java.util.List;
import org.dynmap.Client;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.MapTile;
import org.dynmap.MapType;
import org.dynmap.storage.MapStorage;
import org.dynmap.storage.MapStorageTile;
import org.dynmap.storage.MapStorageTileEnumCB;
import org.dynmap.utils.TileFlags;
import org.json.simple.JSONObject;
public class HDMap extends MapType {
private String name;
private String prefix;
private HDPerspective perspective;
private HDShader shader;
private HDLighting lighting;
// private ConfigurationNode configuration;
private int mapzoomout;
private String imgfmtstring;
private MapType.ImageFormat imgformat;
private int bgcolornight;
private int bgcolorday;
private int tilescale;
private String title;
private String icon;
private String bg_cfg;
private String bg_day_cfg;
private String bg_night_cfg;
private String append_to_world;
private int mapzoomin;
private int boostzoom;
public DynmapCore core;
public static final String IMGFORMAT_PNG = "png";
public static final String IMGFORMAT_JPG = "jpg";
public HDMap(DynmapCore core, ConfigurationNode configuration) {
this.core = core;
name = configuration.getString("name", null);
if(name == null) {
Log.severe("HDMap missing required attribute 'name' - disabled");
return;
}
String perspectiveid = configuration.getString("perspective", "default");
perspective = MapManager.mapman.hdmapman.perspectives.get(perspectiveid);
if(perspective == null) {
/* Try to use default */
perspective = MapManager.mapman.hdmapman.perspectives.get("default");
if(perspective == null) {
Log.severe("HDMap '"+name+"' loaded invalid perspective '" + perspectiveid + "' - map disabled");
name = null;
return;
}
else {
Log.severe("HDMap '"+name+"' loaded invalid perspective '" + perspectiveid + "' - using 'default' perspective");
}
}
String shaderid = configuration.getString("shader", "default");
shader = MapManager.mapman.hdmapman.shaders.get(shaderid);
if(shader == null) {
shader = MapManager.mapman.hdmapman.shaders.get("default");
if(shader == null) {
Log.severe("HDMap '"+name+"' loading invalid shader '" + shaderid + "' - map disabled");
name = null;
return;
}
else {
Log.severe("HDMap '"+name+"' loading invalid shader '" + shaderid + "' - using 'default' shader");
}
}
String lightingid = configuration.getString("lighting", "default");
lighting = MapManager.mapman.hdmapman.lightings.get(lightingid);
if(lighting == null) {
lighting = MapManager.mapman.hdmapman.lightings.get("default");
if(lighting == null) {
Log.severe("HDMap '"+name+"' loading invalid lighting '" + lighting + "' - map disabled");
name = null;
return;
}
else {
Log.severe("HDMap '"+name+"' loading invalid lighting '" + lighting + "' - using 'default' lighting");
}
}
prefix = configuration.getString("prefix", name);
/* Compute extra zoom outs needed for this map */
double scale = perspective.getScale();
mapzoomout = 0;
while(scale >= 1.0) {
mapzoomout++;
scale = scale / 2.0;
}
imgfmtstring = configuration.getString("image-format", "default");
if(imgfmtstring.equals("default")) {
imgformat = ImageFormat.fromID(core.getDefImageFormat());
}
else {
imgformat = ImageFormat.fromID(imgfmtstring);
}
if(imgformat == null) {
Log.severe("HDMap '"+name+"' set invalid image-format: " + imgfmtstring);
imgformat = ImageFormat.FORMAT_PNG;
}
/* Get color info */
String c = configuration.getString("background");
if(c != null) {
bgcolorday = bgcolornight = parseColor(c);
}
c = configuration.getString("backgroundday");
if(c != null) {
bgcolorday = parseColor(c);
}
c = configuration.getString("backgroundnight");
if(c != null) {
bgcolornight = parseColor(c);
}
if(imgformat != ImageFormat.FORMAT_PNG) { /* If JPG, set background color opacity */
bgcolorday |= 0xFF000000;
bgcolornight |= 0xFF000000;
}
this.title = configuration.getString("title", name);
this.icon = configuration.getString("icon");
this.bg_cfg = configuration.getString("background");
this.bg_day_cfg = configuration.getString("backgroundday");
this.bg_night_cfg = configuration.getString("backgroundnight");
this.mapzoomin = configuration.getInteger("mapzoomin", 2);
this.mapzoomout = configuration.getInteger("mapzoomout", this.mapzoomout);
this.boostzoom = configuration.getInteger("boostzoom", 0);
this.tilescale = configuration.getInteger("tilescale", core.getMapManager().getDefaultTileScale()); // 0 = 128, 1 = 256, ...
if (this.tilescale <= 0) this.tilescale = 0;
if (this.tilescale >= 4) this.tilescale = 4; // Limit to 2k x 2k
if(this.boostzoom < 0) this.boostzoom = 0;
if(this.boostzoom > 3) this.boostzoom = 3;
// Map zoom in must be at least as big as boost zoom
if (this.boostzoom > this.mapzoomin) {
this.mapzoomin = this.boostzoom;
}
this.append_to_world = configuration.getString("append_to_world", "");
setProtected(configuration.getBoolean("protected", false));
setTileUpdateDelay(configuration.getInteger("tileupdatedelay", -1));
setReadOnly(configuration.getBoolean("readonly", false));
}
public ConfigurationNode saveConfiguration() {
ConfigurationNode cn = super.saveConfiguration();
cn.put("title", title);
if(icon != null)
cn.put("icon", icon);
cn.put("prefix", prefix);
if(perspective != null)
cn.put("perspective", perspective.getName());
if(shader != null)
cn.put("shader", shader.getName());
if(lighting != null)
cn.put("lighting", lighting.getName());
cn.put("image-format", imgfmtstring);
cn.put("mapzoomin", mapzoomin);
cn.put("mapzoomout", mapzoomout);
cn.put("boostzoom", boostzoom);
cn.put("tilescale", tilescale);
if(bg_cfg != null)
cn.put("background", bg_cfg);
if(bg_day_cfg != null)
cn.put("backgroundday", bg_day_cfg);
if(bg_night_cfg != null)
cn.put("backgroundnight", bg_night_cfg);
cn.put("append_to_world", append_to_world);
cn.put("protected", isProtected());
cn.put("readonly", isReadOnly());
if(this.tileupdatedelay > 0) {
cn.put("tileupdatedelay", this.tileupdatedelay);
}
return cn;
}
public final HDShader getShader() { return shader; }
public final HDPerspective getPerspective() { return perspective; }
public final HDLighting getLighting() { return lighting; }
public final int getBoostZoom() { return boostzoom; }
@Override
public final int getTileSize() { return 128 << tilescale; }
public final int getTileScale() { return tilescale; }
@Override
public List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int x, int y, int z) {
return perspective.getTileCoords(w, x, y, z, tilescale);
}
@Override
public List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int minx, int miny, int minz, int maxx, int maxy, int maxz) {
return perspective.getTileCoords(w, minx, miny, minz, maxx, maxy, maxz, tilescale);
}
@Override
public MapTile[] getAdjecentTiles(MapTile tile) {
return perspective.getAdjecentTiles(tile);
}
@Override
public List<DynmapChunk> getRequiredChunks(MapTile tile) {
return perspective.getRequiredChunks(tile);
}
/* Return number of zoom levels needed by this map (before extra levels from extrazoomout) */
public int getMapZoomOutLevels() {
return mapzoomout;
}
@Override
public String getName() {
return name;
}
@Override
public String getPrefix() {
return prefix;
}
/* Get maps rendered concurrently with this map in this world */
public List<MapType> getMapsSharingRender(DynmapWorld w) {
ArrayList<MapType> maps = new ArrayList<MapType>();
for(MapType mt : w.maps) {
if(mt instanceof HDMap) {
HDMap hdmt = (HDMap)mt;
if((hdmt.perspective == this.perspective) && (hdmt.boostzoom == this.boostzoom)) { /* Same perspective */
maps.add(hdmt);
}
}
}
return maps;
}
/* Get names of maps rendered concurrently with this map type in this world */
public List<String> getMapNamesSharingRender(DynmapWorld w) {
ArrayList<String> lst = new ArrayList<String>();
for(MapType mt : w.maps) {
if(mt instanceof HDMap) {
HDMap hdmt = (HDMap)mt;
if((hdmt.perspective == this.perspective) && (hdmt.boostzoom == this.boostzoom)) { /* Same perspective */
if(hdmt.lighting.isNightAndDayEnabled())
lst.add(hdmt.getName() + "(night/day)");
else
lst.add(hdmt.getName());
}
}
}
return lst;
}
@Override
public ImageFormat getImageFormat() { return imgformat; }
@Override
public void buildClientConfiguration(JSONObject worldObject, DynmapWorld world) {
JSONObject o = new JSONObject();
s(o, "type", "HDMapType");
s(o, "name", name);
s(o, "title", title);
s(o, "icon", icon);
s(o, "prefix", prefix);
s(o, "background", bg_cfg);
s(o, "backgroundday", bg_day_cfg);
s(o, "backgroundnight", bg_night_cfg);
s(o, "bigmap", true);
s(o, "mapzoomout", (world.getExtraZoomOutLevels()+mapzoomout));
s(o, "mapzoomin", mapzoomin);
s(o, "boostzoom", boostzoom);
s(o, "tilescale", tilescale);
s(o, "protected", isProtected());
s(o, "image-format", imgformat.getFileExt());
if(append_to_world.length() > 0)
s(o, "append_to_world", append_to_world);
perspective.addClientConfiguration(o);
shader.addClientConfiguration(o);
lighting.addClientConfiguration(o);
a(worldObject, "maps", o);
}
private static int parseColor(String c) {
int v = 0;
if(c.startsWith("#")) {
c = c.substring(1);
if(c.length() == 3) { /* #rgb */
try {
v = Integer.valueOf(c, 16);
} catch (NumberFormatException nfx) {
return 0;
}
v = 0xFF000000 | ((v & 0xF00) << 12) | ((v & 0x0F0) << 8) | ((v & 0x00F) << 4);
}
else if(c.length() == 6) { /* #rrggbb */
try {
v = Integer.valueOf(c, 16);
} catch (NumberFormatException nfx) {
return 0;
}
v = 0xFF000000 | (v & 0xFFFFFF);
}
}
return v;
}
public int getBackgroundARGBDay() {
return bgcolorday;
}
public int getBackgroundARGBNight() {
return bgcolornight;
}
public void purgeOldTiles(final DynmapWorld world, final TileFlags rendered) {
final MapStorage ms = world.getMapStorage();
ms.enumMapTiles(world, this, new MapStorageTileEnumCB() {
@Override
public void tileFound(MapStorageTile tile, ImageEncoding fmt) {
if (fmt != getImageFormat().getEncoding()) { // Wrong format? toss it
/* Otherwise, delete tile */
tile.delete();
}
else if (tile.zoom == 1) { // First tier zoom? sensitive to newly rendered tiles
// If any were rendered, already triggered (and still needed
if (rendered.getFlag(tile.x, tile.y) || rendered.getFlag(tile.x+1, tile.y) ||
rendered.getFlag(tile.x, tile.y-1) || rendered.getFlag(tile.x+1, tile.y-1)) {
return;
}
tile.enqueueZoomOutUpdate();
}
else if (tile.zoom == 0) {
if (rendered.getFlag(tile.x, tile.y)) { /* If we rendered this tile, its good */
return;
}
/* Otherwise, delete tile */
tile.delete();
/* Push updates, clear hash code, and signal zoom tile update */
MapManager.mapman.pushUpdate(world, new Client.Tile(tile.getURI()));
tile.enqueueZoomOutUpdate();
}
}
});
}
public String getTitle() {
return title;
}
public int getMapZoomIn() {
return mapzoomin;
}
public String getIcon() {
return (icon == null)?"":icon;
}
public boolean setPrefix(String s) {
if(!s.equals(prefix)) {
prefix = s;
return true;
}
return false;
}
public boolean setTitle(String s) {
if(!s.equals(title)) {
title = s;
return true;
}
return false;
}
public boolean setAppendToWorld(String s) {
if(!s.equals(append_to_world)) {
append_to_world = s;
return true;
}
return false;
}
public String getAppendToWorld() {
return append_to_world;
}
public boolean setMapZoomIn(int mzi) {
if(mzi != mapzoomin) {
mapzoomin = mzi;
return true;
}
return false;
}
public boolean setMapZoomOut(int mzi) {
if(mzi != mapzoomout) {
mapzoomout = mzi;
return true;
}
return false;
}
public boolean setBoostZoom(int mzi) {
if(mzi != this.boostzoom) {
this.boostzoom = mzi;
return true;
}
return false;
}
public boolean setTileScale(int mzi) {
if(mzi != this.tilescale) {
this.tilescale = mzi;
return true;
}
return false;
}
public boolean setPerspective(HDPerspective p) {
if(perspective != p) {
perspective = p;
return true;
}
return false;
}
public boolean setShader(HDShader p) {
if(shader != p) {
shader = p;
return true;
}
return false;
}
public boolean setLighting(HDLighting p) {
if(lighting != p) {
lighting = p;
return true;
}
return false;
}
public boolean setImageFormatSetting(String f) {
if(imgfmtstring.equals(f) == false) {
MapType.ImageFormat newfmt;
if(f.equals("default"))
newfmt = MapType.ImageFormat.fromID(core.getDefImageFormat());
else
newfmt = MapType.ImageFormat.fromID(f);
if(newfmt != null) {
imgformat = newfmt;
imgfmtstring = f;
return true;
}
}
return false;
}
public String getImageFormatSetting() {
return imgfmtstring;
}
public boolean setIcon(String v) {
if("".equals(v)) v = null;
icon = v;
return true;
}
@Override
public void addMapTiles(List<MapTile> list, DynmapWorld w, int tx, int ty) {
list.add(new HDMapTile(w, this.perspective, tx, ty, boostzoom, tilescale));
}
private static final ImageVariant[] dayVariant = { ImageVariant.STANDARD, ImageVariant.DAY };
@Override
public ImageVariant[] getVariants() {
if (lighting.isNightAndDayEnabled())
return dayVariant;
return super.getVariants();
}
}

View file

@ -0,0 +1,196 @@
package org.dynmap.hdmap;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.MapType;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
public class HDMapManager {
public HashMap<String, HDShader> shaders = new HashMap<String, HDShader>();
public HashMap<String, HDPerspective> perspectives = new HashMap<String, HDPerspective>();
public HashMap<String, HDLighting> lightings = new HashMap<String, HDLighting>();
public HashSet<HDMap> maps = new HashSet<HDMap>();
public HashMap<String, ArrayList<HDMap>> maps_by_world_perspective = new HashMap<String, ArrayList<HDMap>>();
public void loadHDShaders(DynmapCore core) {
Log.verboseinfo("Loading shaders...");
/* Update mappings, if needed */
TexturePack.handleBlockAlias();
File f = new File(core.getDataFolder(), "shaders.txt");
if(!core.updateUsingDefaultResource("/shaders.txt", f, "shaders")) {
return;
}
ConfigurationNode shadercfg = new ConfigurationNode(f);
shadercfg.load();
for(HDShader shader : shadercfg.<HDShader>createInstances("shaders", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(shader.getName() == null) continue;
shaders.put(shader.getName(), shader);
}
/* Load custom shaders, if file is defined - or create empty one if not */
f = new File(core.getDataFolder(), "custom-shaders.txt");
core.createDefaultFileFromResource("/custom-shaders.txt", f);
if(f.exists()) {
ConfigurationNode customshadercfg = new ConfigurationNode(f);
customshadercfg.load();
for(HDShader shader : customshadercfg.<HDShader>createInstances("shaders", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(shader.getName() == null) continue;
shaders.put(shader.getName(), shader);
}
}
Log.info("Loaded " + shaders.size() + " shaders.");
}
public void loadHDPerspectives(DynmapCore core) {
Log.verboseinfo("Loading perspectives...");
// Update mappings, if needed
HDBlockModels.handleBlockAlias();
File f = new File(core.getDataFolder(), "perspectives.txt");
if(!core.updateUsingDefaultResource("/perspectives.txt", f, "perspectives")) {
return;
}
ConfigurationNode perspectivecfg = new ConfigurationNode(f);
perspectivecfg.load();
for(HDPerspective perspective : perspectivecfg.<HDPerspective>createInstances("perspectives", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(perspective.getName() == null) continue;
perspectives.put(perspective.getName(), perspective);
}
/* Load custom perspectives, if file is defined - or create empty one if not */
f = new File(core.getDataFolder(), "custom-perspectives.txt");
core.createDefaultFileFromResource("/custom-perspectives.txt", f);
if(f.exists()) {
perspectivecfg = new ConfigurationNode(f);
perspectivecfg.load();
for(HDPerspective perspective : perspectivecfg.<HDPerspective>createInstances("perspectives", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(perspective.getName() == null) continue;
perspectives.put(perspective.getName(), perspective);
}
}
Log.info("Loaded " + perspectives.size() + " perspectives.");
}
public void loadHDLightings(DynmapCore core) {
Log.verboseinfo("Loading lightings...");
File f = new File(core.getDataFolder(), "lightings.txt");
if(!core.updateUsingDefaultResource("/lightings.txt", f, "lightings")) {
return;
}
ConfigurationNode lightingcfg = new ConfigurationNode(f);
lightingcfg.load();
for(HDLighting lighting : lightingcfg.<HDLighting>createInstances("lightings", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(lighting.getName() == null) continue;
lightings.put(lighting.getName(), lighting);
}
/* Load custom lightings, if file is defined - or create empty one if not */
f = new File(core.getDataFolder(), "custom-lightings.txt");
core.createDefaultFileFromResource("/custom-lightings.txt", f);
if(f.exists()) {
lightingcfg = new ConfigurationNode(f);
lightingcfg.load();
for(HDLighting lighting : lightingcfg.<HDLighting>createInstances("lightings", new Class<?>[] { DynmapCore.class }, new Object[] { core })) {
if(lighting.getName() == null) continue;
lightings.put(lighting.getName(), lighting);
}
}
Log.info("Loaded " + lightings.size() + " lightings.");
}
/**
* Initialize shader states for all shaders for given tile
*
* @param tile - tile to init
* @param cache - chunk cache
* @param mapiter - map iterator
* @param mapname - map name
* @param scale - map scale
* @return array of shader states for all associated shaders
*/
public HDShaderState[] getShaderStateForTile(HDMapTile tile, MapChunkCache cache, MapIterator mapiter, String mapname, int scale) {
DynmapWorld w = MapManager.mapman.worldsLookup.get(tile.getDynmapWorld().getName());
if(w == null) {
return new HDShaderState[0];
}
ArrayList<HDShaderState> shaders = new ArrayList<HDShaderState>();
for(MapType map : w.maps) {
if(map instanceof HDMap) {
HDMap hdmap = (HDMap)map;
// If same perspective, at same scale (tile and boost), render together
if((hdmap.getPerspective() == tile.perspective) && (hdmap.getBoostZoom() == tile.boostzoom) && (hdmap.getTileScale() == tile.tilescale)) {
/* If limited to one map, and this isn't it, skip */
if((mapname != null) && (!hdmap.getName().equals(mapname)))
continue;
// Maps can be set to read-only, which means they don't get re-rendered
if (map.isReadOnly()) {
continue;
}
shaders.add(hdmap.getShader().getStateInstance(hdmap, cache, mapiter, scale));
}
}
}
return shaders.toArray(new HDShaderState[shaders.size()]);
}
private static final int BIOMEDATAFLAG = 0;
private static final int HIGHESTZFLAG = 1;
private static final int RAWBIOMEFLAG = 2;
private static final int BLOCKTYPEFLAG = 3;
public boolean isBiomeDataNeeded(HDMapTile t) {
return getCachedFlags(t)[BIOMEDATAFLAG];
}
public boolean isHightestBlockYDataNeeded(HDMapTile t) {
return getCachedFlags(t)[HIGHESTZFLAG];
}
public boolean isRawBiomeDataNeeded(HDMapTile t) {
return getCachedFlags(t)[RAWBIOMEFLAG];
}
public boolean isBlockTypeDataNeeded(HDMapTile t) {
return getCachedFlags(t)[BLOCKTYPEFLAG];
}
private HashMap<String, boolean[]> cached_data_flags_by_world_perspective = new HashMap<String, boolean[]>();
private boolean[] getCachedFlags(HDMapTile t) {
String w = t.getDynmapWorld().getName();
String k = w + "/" + t.perspective.getName();
boolean[] flags = cached_data_flags_by_world_perspective.get(k);
if(flags != null)
return flags;
flags = new boolean[4];
cached_data_flags_by_world_perspective.put(k, flags);
DynmapWorld dw = MapManager.mapman.worldsLookup.get(w);
if(dw == null) return flags;
for(MapType map : dw.maps) {
if(map instanceof HDMap) {
HDMap hdmap = (HDMap)map;
if(hdmap.getPerspective() == t.perspective) {
HDShader sh = hdmap.getShader();
HDLighting lt = hdmap.getLighting();
flags[BIOMEDATAFLAG] |= sh.isBiomeDataNeeded() | lt.isBiomeDataNeeded();
flags[HIGHESTZFLAG] |= sh.isHightestBlockYDataNeeded() | lt.isHightestBlockYDataNeeded();
flags[RAWBIOMEFLAG] |= sh.isRawBiomeDataNeeded() | lt.isRawBiomeDataNeeded();
flags[BLOCKTYPEFLAG] |= sh.isBlockTypeDataNeeded() | lt.isBlockTypeDataNeeded();
}
}
}
return flags;
}
}

View file

@ -0,0 +1,116 @@
package org.dynmap.hdmap;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapWorld;
import org.dynmap.MapManager;
import java.util.List;
import org.dynmap.MapTile;
import org.dynmap.utils.MapChunkCache;
public class HDMapTile extends MapTile {
public final HDPerspective perspective;
public final int tx, ty; /* Tile X and Tile Y are in tile coordinates (pixels/tile-size) */
public final int boostzoom;
public final int tilescale;
public HDMapTile(DynmapWorld world, HDPerspective perspective, int tx, int ty, int boostzoom, int tilescale) {
super(world);
this.perspective = perspective;
this.tx = tx;
this.ty = ty;
this.boostzoom = boostzoom;
this.tilescale = tilescale;
}
// Used for restore of saved pending renders
public HDMapTile(DynmapWorld world, String parm) throws Exception {
super(world);
String[] parms = parm.split(",");
if(parms.length < 3) throw new Exception("wrong parameter count");
this.tx = Integer.parseInt(parms[0]);
this.ty = Integer.parseInt(parms[1]);
this.perspective = MapManager.mapman.hdmapman.perspectives.get(parms[2]);
if(this.perspective == null) throw new Exception("invalid perspective");
if(parms.length > 3)
this.boostzoom = Integer.parseInt(parms[3]);
else
this.boostzoom = 0;
if (parms.length > 4) {
this.tilescale = Integer.parseInt(parms[4]);
}
else {
this.tilescale = 0;
}
}
@Override
protected String saveTileData() {
return String.format("%d,%d,%s,%d,%d", tx, ty, perspective.getName(), boostzoom, tilescale);
}
@Override
public int hashCode() {
int h = perspective.hashCode();
h = h * 31 + world.hashCode();
h = h * 31 + tx;
h = h * 31 + ty;
h = h * 31 + boostzoom;
h = h * 31 + tilescale;
return h;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HDMapTile) {
return equals((HDMapTile) obj);
}
return false;
}
public boolean equals(HDMapTile o) {
return o.tx == tx && o.ty == ty && (perspective == o.perspective) && (o.world == world) && (o.boostzoom == boostzoom) && (o.tilescale == tilescale);
}
@Override
public String toString() {
return world.getName() + ":" + perspective.getName() + "," + tx + "," + ty + ":" + boostzoom + ":" + tilescale;
}
@Override
public int getTileSize() { return 128 << tilescale; }
@Override
public boolean isBiomeDataNeeded() { return MapManager.mapman.hdmapman.isBiomeDataNeeded(this); }
@Override
public boolean isHightestBlockYDataNeeded() { return MapManager.mapman.hdmapman.isHightestBlockYDataNeeded(this); }
@Override
public boolean isRawBiomeDataNeeded() { return MapManager.mapman.hdmapman.isRawBiomeDataNeeded(this); }
@Override
public boolean isBlockTypeDataNeeded() { return MapManager.mapman.hdmapman.isBlockTypeDataNeeded(this); }
@Override
public boolean render(MapChunkCache cache, String mapname) {
return perspective.render(cache, this, mapname);
}
@Override
public List<DynmapChunk> getRequiredChunks() {
return perspective.getRequiredChunks(this);
}
@Override
public MapTile[] getAdjecentTiles() {
return perspective.getAdjecentTiles(this);
}
@Override
public int tileOrdinalX() { return tx; }
@Override
public int tileOrdinalY() { return ty; }
}

View file

@ -0,0 +1,40 @@
package org.dynmap.hdmap;
import java.util.List;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapWorld;
import org.dynmap.MapTile;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.TileFlags;
import org.dynmap.utils.Vector3D;
import org.json.simple.JSONObject;
public interface HDPerspective {
/* Get name of perspective */
String getName();
/* Get tiles invalidated by change at given location */
List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int x, int y, int z, int tilescale);
/* Get tiles invalidated by change at given volume, defined by 2 opposite corner locations */
List<TileFlags.TileCoord> getTileCoords(DynmapWorld w, int minx, int miny, int minz, int maxx, int maxy, int maxz, int tilescale);
/* Get tiles adjacent to given tile */
MapTile[] getAdjecentTiles(MapTile tile);
/* Get chunks needed for given tile */
List<DynmapChunk> getRequiredChunks(MapTile tile);
/* Render given tile */
boolean render(MapChunkCache cache, HDMapTile tile, String mapname);
public boolean isBiomeDataNeeded();
public boolean isHightestBlockYDataNeeded();
public boolean isRawBiomeDataNeeded();
public boolean isBlockTypeDataNeeded();
double getScale();
int getModelScale();
public void addClientConfiguration(JSONObject mapObject);
public void transformWorldToMapCoord(Vector3D input, Vector3D rslt);
public int hashCode();
}

View file

@ -0,0 +1,100 @@
package org.dynmap.hdmap;
import org.dynmap.utils.MapIterator;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.Vector3D;
import org.dynmap.utils.LightLevels;
public interface HDPerspectiveState {
/**
* Get light levels - only available if shader requested it
* @param ll - light levels (filled in when returned)
*/
void getLightLevels(LightLevels ll);
/**
* Get sky light level - only available if shader requested it
* @param step - last step
* @param ll - light levels (filled in when returned)
*/
void getLightLevelsAtStep(BlockStep step, LightLevels ll);
/**
* Get current block state
* @return block
*/
DynmapBlockState getBlockState();
/**
* Get direction of last block step
* @return last step direction
*/
BlockStep getLastBlockStep();
/**
* Get perspective scale
* @return scale
*/
double getScale();
/**
* Get start of current ray, in world coordinates
* @return start of ray
*/
Vector3D getRayStart();
/**
* Get end of current ray, in world coordinates
* @return end of ray
*/
Vector3D getRayEnd();
/**
* Get pixel X coordinate
* @return x coordinate
*/
int getPixelX();
/**
* Get pixel Y coordinate
* @return y coordinate
*/
int getPixelY();
/**
* Get current patch shade setting (false = no shadows)
*/
boolean getShade();
/**
* Return submodel alpha value (-1 if no submodel rendered)
* @return alpha value
*/
int getSubmodelAlpha();
/**
* Return subblock coordinates of current ray position
* @return coordinates of ray
*/
int[] getSubblockCoord();
/**
* Check if point is on face
*/
boolean isOnFace();
/**
* Get map iterator
* @return iterator
*/
MapIterator getMapIterator();
/**
* Get current texture index
* @return texture index
*/
int getTextureIndex();
/**
* Get current U of patch intercept
* @return U in patch
*/
double getPatchU();
/**
* Get current V of patch intercept
* @return V in patch
*/
double getPatchV();
/**
* Light level cache
* @param idx - index of light level (0-3)
* @return light level
*/
LightLevels getCachedLightLevels(int idx);
}

View file

@ -0,0 +1,92 @@
package org.dynmap.hdmap;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.PatchDefinition;
public class HDScaledBlockModels {
private short[][] modelvectors;
// These are scale invariant - only need once
private static PatchDefinition[][] patches;
private static CustomBlockModel[] custom;
public HDScaledBlockModels(int scale) {
short[][] blockmodels = new short[DynmapBlockState.getGlobalIndexMax()][];
PatchDefinition[][] newpatches = null;
if (patches == null) {
newpatches = new PatchDefinition[DynmapBlockState.getGlobalIndexMax()][];
patches = newpatches;
}
CustomBlockModel[] newcustom = null;
if (custom == null) {
newcustom = new CustomBlockModel[DynmapBlockState.getGlobalIndexMax()];
custom = newcustom;
}
for(int gidx = 0; gidx < HDBlockModels.models_by_id_data.length; gidx++) {
HDBlockModel m = HDBlockModels.models_by_id_data[gidx];
if(m == null) continue;
if(m instanceof HDBlockVolumetricModel) {
HDBlockVolumetricModel vm = (HDBlockVolumetricModel)m;
short[] smod = vm.getScaledMap(scale);
/* See if scaled model is full block : much faster to not use it if it is */
if(smod != null) {
boolean keep = false;
for(int i = 0; (!keep) && (i < smod.length); i++) {
if(smod[i] == 0) keep = true;
}
if(keep) {
blockmodels[gidx] = smod;
}
else {
blockmodels[gidx] = null;
}
}
}
else if(m instanceof HDBlockPatchModel) {
if (newpatches != null) {
HDBlockPatchModel pm = (HDBlockPatchModel)m;
newpatches[gidx] = pm.getPatches();
}
}
else if(m instanceof CustomBlockModel) {
if (newcustom != null) {
CustomBlockModel cbm = (CustomBlockModel)m;
newcustom[gidx] = cbm;
}
}
}
this.modelvectors = blockmodels;
}
public final short[] getScaledModel(DynmapBlockState blk) {
int idx = blk.globalStateIndex;
if(idx >= modelvectors.length) {
short[][] newmodels = new short[idx + 1][];
System.arraycopy(modelvectors, 0, newmodels, 0, modelvectors.length);
modelvectors = newmodels;
return null;
}
return modelvectors[idx];
}
public PatchDefinition[] getPatchModel(DynmapBlockState blk) {
int idx = blk.globalStateIndex;
if(idx >= patches.length) {
PatchDefinition[][] newpatches = new PatchDefinition[idx + 1][];
System.arraycopy(patches, 0, newpatches, 0, patches.length);
patches = newpatches;
return null;
}
return patches[idx];
}
public CustomBlockModel getCustomBlockModel(DynmapBlockState blk) {
int idx = blk.globalStateIndex;
if(idx >= custom.length) {
CustomBlockModel[] newcustom = new CustomBlockModel[idx + 1];
System.arraycopy(custom, 0, newcustom, 0, custom.length);
custom = newcustom;
return null;
}
return custom[idx];
}
}

View file

@ -0,0 +1,43 @@
package org.dynmap.hdmap;
import java.io.IOException;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
public interface HDShader {
/* Get shader name */
String getName();
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale
* @return state object to use for all rays in tile
*/
HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale);
/* Test if Biome Data is needed for this renderer */
boolean isBiomeDataNeeded();
/* Test if raw biome temperature/rainfall data is needed */
boolean isRawBiomeDataNeeded();
/* Test if highest block Y data is needed */
boolean isHightestBlockYDataNeeded();
/* Tet if block type data needed */
boolean isBlockTypeDataNeeded();
/* Test if sky light level needed */
boolean isSkyLightLevelNeeded();
/* Test if emitted light level needed */
boolean isEmittedLightLevelNeeded();
/* Add shader's contributions to JSON for map object */
void addClientConfiguration(JSONObject mapObject);
/* Export shader as material library */
void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport exp) throws IOException;
/* Get materials for each patch on the current block (with +N for N*90 degree rotations) */
String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps);
}

View file

@ -0,0 +1,67 @@
package org.dynmap.hdmap;
import org.dynmap.Color;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.DynLongHashMap;
/**
* This interface is used to define the operational state of a renderer during raytracing
* All method should be considered performance critical
*/
public interface HDShaderState {
/**
* Get our shader
* @return shader
*/
HDShader getShader();
/**
* Get our lighting
* @return lighting
*/
HDLighting getLighting();
/**
* Get our map
* @return map
*/
HDMap getMap();
/**
* Reset renderer state for new ray - passes in pixel coordinate for ray
* @param ps - perspective state
*/
void reset(HDPerspectiveState ps);
/**
* Process next ray step - called for each block on route
* @param ps - perspective state
* @return true if ray is done, false if ray needs to continue
*/
boolean processBlock(HDPerspectiveState ps);
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
* @param ps - perspective state
*/
void rayFinished(HDPerspectiveState ps);
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
void getRayColor(Color c, int index);
/**
* Clean up state object - called after last ray completed
*/
void cleanup();
/**
* Get CTM texture cache
* @return texture cache
*/
DynLongHashMap getCTMTextureCache();
/**
* Get lighting table
* @return array of lighting values
*/
int[] getLightingTable();
/**
* Update last block state (called before moving to next block)
*/
void setLastBlockState(DynmapBlockState new_lastbs);
}

View file

@ -0,0 +1,244 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import java.util.HashMap;
import java.util.TreeSet;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
public class InhabitedHDShader implements HDShader {
private final String name;
private final long filllevel[]; /* Values for colors */
private final Color fillcolor[];
private Color readColor(String id, ConfigurationNode cfg) {
String lclr = cfg.getString(id, null);
if((lclr != null) && (lclr.startsWith("#"))) {
try {
int c = Integer.parseInt(lclr.substring(1), 16);
return new Color((c>>16)&0xFF, (c>>8)&0xFF, c&0xFF);
} catch (NumberFormatException nfx) {
Log.severe("Invalid color value: " + lclr + " for '" + id + "'");
}
}
return null;
}
public InhabitedHDShader(DynmapCore core, ConfigurationNode configuration) {
name = (String) configuration.get("name");
HashMap<Long, Color> map = new HashMap<Long, Color>();
for (String key : configuration.keySet()) {
if (key.startsWith("color")) {
try {
long val = Long.parseLong(key.substring(5));
Color clr = readColor(key, configuration);
map.put(val, clr);
} catch (NumberFormatException nfx) {
}
}
}
TreeSet<Long> keys = new TreeSet<Long>(map.keySet());
filllevel = new long[keys.size()];
fillcolor = new Color[keys.size()];
int idx = 0;
for (Long k : keys) {
filllevel[idx] = k;
fillcolor[idx] = map.get(k);
idx++;
}
}
@Override
public boolean isBiomeDataNeeded() {
return false;
}
@Override
public boolean isRawBiomeDataNeeded() {
return false;
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return false;
}
@Override
public String getName() {
return name;
}
private class OurShaderState implements HDShaderState {
private Color color[];
private Color c;
protected HDMap map;
private HDLighting lighting;
final int[] lightingTable;
private OurShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
this.map = map;
this.lighting = map.getLighting();
if(lighting.isNightAndDayEnabled()) {
color = new Color[] { new Color(), new Color() };
}
else {
color = new Color[] { new Color() };
}
c = new Color();
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
public HDShader getShader() {
return InhabitedHDShader.this;
}
/**
* Get our map
*/
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
public HDLighting getLighting() {
return lighting;
}
/**
* Reset renderer state for new ray
*/
public void reset(HDPerspectiveState ps) {
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
if (ps.getBlockState().isAir()) {
return false;
}
long ts = ps.getMapIterator().getInhabitedTicks() / 1200; // Get time, in minutes
// Find top of range
boolean match = false;
for (int i = 0; i < filllevel.length; i++) {
if (ts < filllevel[i]) { // Found it
if (i > 0) { // Middle? Interpolate
int interp = (int) ((256 * (ts - filllevel[i-1])) / (filllevel[i] - filllevel[i-1]));
int red = (interp * fillcolor[i].getRed()) + ((256 - interp) * fillcolor[i-1].getRed());
int green = (interp * fillcolor[i].getGreen()) + ((256 - interp) * fillcolor[i-1].getGreen());
int blue = (interp * fillcolor[i].getBlue()) + ((256 - interp) * fillcolor[i-1].getBlue());
c.setRGBA(red / 256, green / 256, blue / 256, 255);
}
else { // Else, use color
c.setColor(fillcolor[i]);
}
match = true;
break;
}
}
if (!match) {
c.setColor(fillcolor[fillcolor.length-1]);
}
/* Handle light level, if needed */
lighting.applyLighting(ps, this, c, color);
return true;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
public void getRayColor(Color c, int index) {
c.setColor(color[index]);
}
/**
* Clean up state object - called after last ray completed
*/
public void cleanup() {
}
@Override
public DynLongHashMap getCTMTextureCache() {
return null;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of perspecitve
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new OurShaderState(mapiter, map, cache, scale);
}
/* Add shader's contributions to JSON for map object */
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
throw new IOException("Export unsupported");
}
private static final String[] nulllist = new String[0];
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps) {
return nulllist;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
package org.dynmap.hdmap;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.utils.LightLevels;
public class LightLevelHDLighting extends DefaultHDLighting {
private final Color[] lightlevelcolors = new Color[16];
protected final boolean night_and_day; /* If true, render both day (prefix+'-day') and night (prefix) tiles */
private final boolean night;
private final Color mincolor = new Color(0x40, 0x40, 0x40);
private final Color maxcolor = new Color(0xFF, 0xFF, 0xFF);
public LightLevelHDLighting(DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
grayscale = true; // Force to grayscale
blackandwhite = false;
for (int i = 0; i < 16; i++) {
lightlevelcolors[i] = configuration.getColor("color" + i, null);
}
night = configuration.getBoolean("night", false);
night_and_day = configuration.getBoolean("night-and-day", false);
}
/* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */
public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) {
super.applyLighting(ps, ss, incolor, outcolor); // Apply default lighting (outcolors will be grayscale)
// Compute light levels
LightLevels ll = ps.getCachedLightLevels(0);
ps.getLightLevels(ll);
if (outcolor.length == 1) {
// Scale range between 25% and 100% intensity (we don't want blacks, since that will prevent color from showing
outcolor[0].scaleColor(mincolor, maxcolor);
int lightlevel = night ? ll.emitted : Math.max(ll.sky, ll.emitted);
if (lightlevelcolors[lightlevel] != null) {
outcolor[0].blendColor(lightlevelcolors[lightlevel]);
}
}
else {
// Scale range between 25% and 100% intensity (we don't want blacks, since that will prevent color from showing
outcolor[0].scaleColor(mincolor, maxcolor);
outcolor[1].scaleColor(mincolor, maxcolor);
int daylightlevel = Math.max(ll.sky, ll.emitted);
if (lightlevelcolors[ll.emitted] != null) {
outcolor[0].blendColor(lightlevelcolors[ll.emitted]);
}
if (lightlevelcolors[daylightlevel] != null) {
outcolor[1].blendColor(lightlevelcolors[daylightlevel]);
}
}
}
/* Test if night/day is enabled for this renderer */
public boolean isNightAndDayEnabled() { return night_and_day; }
/* Test if sky light level needed */
public boolean isSkyLightLevelNeeded() { return true; }
/* Test if emitted light level needed */
public boolean isEmittedLightLevelNeeded() { return true; }
@Override
public int[] getBrightnessTable(DynmapWorld world) {
return null;
}
}

View file

@ -0,0 +1,196 @@
package org.dynmap.hdmap;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.DynmapWorld;
import org.dynmap.MapManager;
import org.dynmap.utils.LightLevels;
import org.dynmap.utils.BlockStep;
public class ShadowHDLighting extends DefaultHDLighting {
protected final int defLightingTable[]; /* index=skylight level, value = 256 * scaling value */
protected final int lightscale[]; /* scale skylight level (light = lightscale[skylight] */
protected final boolean night_and_day; /* If true, render both day (prefix+'-day') and night (prefix) tiles */
protected final boolean smooth;
protected final boolean useWorldBrightnessTable;
public ShadowHDLighting(DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
double shadowweight = configuration.getDouble("shadowstrength", 0.0);
// See if we're using world's lighting table, or our own
useWorldBrightnessTable = configuration.getBoolean("use-brightness-table", MapManager.mapman.useBrightnessTable());
defLightingTable = new int[16];
defLightingTable[15] = 256;
/* Normal brightness weight in MC is a 20% relative dropoff per step */
for(int i = 14; i >= 0; i--) {
double v = defLightingTable[i+1] * (1.0 - (0.2 * shadowweight));
defLightingTable[i] = (int)v;
if(defLightingTable[i] > 256) defLightingTable[i] = 256;
if(defLightingTable[i] < 0) defLightingTable[i] = 0;
}
int v = configuration.getInteger("ambientlight", -1);
if(v < 0) v = 15;
if(v > 15) v = 15;
night_and_day = configuration.getBoolean("night-and-day", false);
lightscale = new int[16];
for(int i = 0; i < 16; i++) {
if(i < (15-v))
lightscale[i] = 0;
else
lightscale[i] = i - (15-v);
}
smooth = configuration.getBoolean("smooth-lighting", MapManager.mapman.getSmoothLighting());
}
private void applySmoothLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor, int[] shadowscale) {
int[] xyz = ps.getSubblockCoord();
int scale = (int)ps.getScale();
int mid = scale / 2;
BlockStep s1, s2;
int w1, w2;
/* Figure out which two neighbor directions to sample */
switch(ps.getLastBlockStep()) {
case X_MINUS:
case X_PLUS:
s1 = (xyz[1] < mid) ? BlockStep.Y_MINUS : BlockStep.Y_PLUS;
w1 = Math.abs(xyz[1] - mid);
s2 = (xyz[2] < mid) ? BlockStep.Z_MINUS : BlockStep.Z_PLUS;
w2 = Math.abs(xyz[2] - mid);
break;
case Z_MINUS:
case Z_PLUS:
s1 = (xyz[0] < mid) ? BlockStep.X_MINUS : BlockStep.X_PLUS;
w1 = Math.abs(xyz[0] - mid);
s2 = (xyz[1] < mid) ? BlockStep.Y_MINUS : BlockStep.Y_PLUS;
w2 = Math.abs(xyz[1] - mid);
break;
default:
s1 = (xyz[0] < mid) ? BlockStep.X_MINUS : BlockStep.X_PLUS;
w1 = Math.abs(xyz[0] - mid);
s2 = (xyz[2] < mid) ? BlockStep.Z_MINUS : BlockStep.Z_PLUS;
w2 = Math.abs(xyz[2] - mid);
break;
}
/* Fetch the 3 needed light levels (once, shared by both night and day passes) */
LightLevels ll0 = ps.getCachedLightLevels(0);
ps.getLightLevels(ll0);
LightLevels ll1 = ps.getCachedLightLevels(1);
ps.getLightLevelsAtStep(s1, ll1);
LightLevels ll2 = ps.getCachedLightLevels(2);
ps.getLightLevelsAtStep(s2, ll2);
/* Night (ambient) pass */
applySmoothedLightToColor(outcolor[0], incolor, ll0, ll1, ll2, true, w1, w2, scale, shadowscale);
/* Day pass (only when night/day rendering is active) */
if(outcolor.length > 1) {
applySmoothedLightToColor(outcolor[1], incolor, ll0, ll1, ll2, false, w1, w2, scale, shadowscale);
}
}
/** Apply smooth lighting to a single output color for one pass (ambient or day). */
private void applySmoothedLightToColor(Color out, Color incolor,
LightLevels ll0, LightLevels ll1, LightLevels ll2,
boolean useambient, int w1, int w2, int scale, int[] shadowscale) {
int lv0 = getLightLevel(ll0, useambient);
int lv1 = getLightLevel(ll1, useambient);
int weight = 0;
if(lv1 < lv0) weight -= w1;
else if(lv1 > lv0) weight += w1;
int lv2 = getLightLevel(ll2, useambient);
if(lv2 < lv0) weight -= w2;
else if(lv2 > lv0) weight += w2;
out.setColor(incolor);
int cscale = computeSmoothedCscale(lv0, weight, scale, shadowscale);
if(cscale < 256) {
out.scaleRGB(cscale);
}
}
/** Interpolate the shadow scale for a light level, blending toward the adjacent level by weight/scale. */
private int computeSmoothedCscale(int ll0, int weight, int scale, int[] shadowscale) {
if(weight == 0) {
return shadowscale[ll0];
}
if(weight < 0) {
weight = -weight;
return (ll0 > 0)
? (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 - 1] * weight) / scale
: shadowscale[ll0];
}
return (ll0 < 15)
? (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 + 1] * weight) / scale
: shadowscale[ll0];
}
private final int getLightLevel(final LightLevels ll, boolean useambient) {
int lightlevel = useambient ? lightscale[ll.sky] : ll.sky;
if(lightlevel < 15) {
lightlevel = Math.max(ll.emitted, lightlevel);
}
return lightlevel;
}
/* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */
public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) {
int[] shadowscale = ss.getLightingTable();
if(shadowscale == null) {
shadowscale = defLightingTable;
}
if(smooth && ps.getShade()) {
applySmoothLighting(ps, ss, incolor, outcolor, shadowscale);
checkGrayscale(outcolor);
return;
}
/* Non-smooth: fetch light levels and apply flat shadow */
LightLevels ll = ps.getCachedLightLevels(0);
ps.getLightLevels(ll);
int lightlevel = lightscale[ll.sky]; // apply ambient scale immediately
int lightlevel_day = ll.sky;
if((lightlevel < 15) || (lightlevel_day < 15)) {
int emitted = ll.emitted;
lightlevel = Math.max(emitted, lightlevel);
lightlevel_day = Math.max(emitted, lightlevel_day);
}
outcolor[0].setColor(incolor);
if(lightlevel < 15) {
int s = shadowscale[lightlevel];
if(s < 256) outcolor[0].scaleRGB(s);
}
if(outcolor.length > 1) {
if(lightlevel_day == lightlevel) {
outcolor[1].setColor(outcolor[0]);
}
else {
outcolor[1].setColor(incolor);
if(lightlevel_day < 15) {
int s = shadowscale[lightlevel_day];
if(s < 256) outcolor[1].scaleRGB(s);
}
}
}
checkGrayscale(outcolor);
}
/* Test if night/day is enabled for this renderer */
public boolean isNightAndDayEnabled() { return night_and_day; }
/* Test if sky light level needed */
public boolean isSkyLightLevelNeeded() { return true; }
/* Test if emitted light level needed */
public boolean isEmittedLightLevelNeeded() { return true; }
@Override
public int[] getBrightnessTable(DynmapWorld world) {
if (useWorldBrightnessTable) {
return world.getBrightnessTable();
}
else {
return null;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
package org.dynmap.hdmap;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.LightLevels;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
public class TexturePackHDCaveShader extends TexturePackHDShader {
private int maxskylevel;
private int minemittedlevel;
class CaveShaderState extends TexturePackHDShader.ShaderState {
private boolean ready;
private LightLevels ll = new LightLevels();
protected CaveShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
super(mapiter, map, cache, scale);
}
@Override
public void reset(HDPerspectiveState ps) {
super.reset(ps);
ready = false;
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
if(ready)
return super.processBlock(ps);
if((ps.getLastBlockStep() == BlockStep.Y_MINUS) && ps.getBlockState().isAir()) { /* In air? */
ps.getLightLevels(ll);
if((ll.sky <= maxskylevel) && (ll.emitted > minemittedlevel)) {
ready = true;
}
}
return false;
}
}
public TexturePackHDCaveShader(DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
maxskylevel = configuration.getInteger("max-sky-light", 0);
minemittedlevel = configuration.getInteger("min-emitted-light", 1);
}
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new CaveShaderState(mapiter, map, cache, scale);
}
}

View file

@ -0,0 +1,381 @@
package org.dynmap.hdmap;
import static org.dynmap.JSONUtils.s;
import java.io.IOException;
import java.util.BitSet;
import java.util.List;
import org.dynmap.Color;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.common.DynmapCommandSender;
import org.dynmap.exporter.OBJExport;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.BlockStep;
import org.dynmap.utils.DynLongHashMap;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
import org.json.simple.JSONObject;
public class TexturePackHDShader implements HDShader {
private final String tpname;
private final String name;
private TexturePack tp;
private boolean did_tp_load = false;
private final boolean biome_shaded;
private final boolean bettergrass;
private final int gridscale;
private final DynmapCore core;
private final BitSet hiddenids;
public TexturePackHDShader(DynmapCore core, ConfigurationNode configuration) {
tpname = configuration.getString("texturepack", "minecraft");
name = configuration.getString("name", tpname);
this.core = core;
biome_shaded = configuration.getBoolean("biomeshaded", true);
bettergrass = configuration.getBoolean("better-grass", MapManager.mapman.getBetterGrass());
gridscale = configuration.getInteger("grid-scale", 0);
List<Object> hidden = configuration.getList("hiddenids");
if(hidden != null) {
hiddenids = new BitSet();
for(Object o : hidden) {
if(o instanceof Integer) {
int v = ((Integer)o);
hiddenids.set(v);
}
}
}
else {
hiddenids = null;
}
}
private final TexturePack getTexturePack() {
if (!did_tp_load) {
tp = TexturePack.getTexturePack(this.core, this.tpname);
if(tp == null) {
Log.severe("Error: shader '" + name + "' cannot load texture pack '" + tpname + "'");
}
did_tp_load = true;
}
return tp;
}
@Override
public boolean isBiomeDataNeeded() {
return biome_shaded;
}
@Override
public boolean isRawBiomeDataNeeded() {
return false;
}
@Override
public boolean isHightestBlockYDataNeeded() {
return false;
}
@Override
public boolean isBlockTypeDataNeeded() {
return true;
}
@Override
public boolean isSkyLightLevelNeeded() {
return false;
}
@Override
public boolean isEmittedLightLevelNeeded() {
return false;
}
@Override
public String getName() {
return name;
}
class ShaderState implements HDShaderState {
final private Color color[];
final private Color tmpcolor[];
final private Color c;
final protected MapIterator mapiter;
final protected HDMap map;
final private TexturePack scaledtp;
final private HDLighting lighting;
protected DynmapBlockState lastblk;
protected DynmapBlockState lastblkhit;
final boolean do_biome_shading;
final boolean do_better_grass;
DynLongHashMap ctm_cache;
final int[] lightingTable;
protected ShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
this.mapiter = mapiter;
this.map = map;
this.lighting = map.getLighting();
if(lighting.isNightAndDayEnabled()) {
color = new Color[] { new Color(), new Color() };
tmpcolor = new Color[] { new Color(), new Color() };
}
else {
color = new Color[] { new Color() };
tmpcolor = new Color[] { new Color() };
}
c = new Color();
TexturePack tp = getTexturePack();
if (tp != null)
scaledtp = tp.resampleTexturePack(scale);
else
scaledtp = null;
/* Biome raw data only works on normal worlds at this point */
do_biome_shading = biome_shaded; // && (cache.getWorld().getEnvironment() == Environment.NORMAL);
do_better_grass = bettergrass;
if (MapManager.mapman.useBrightnessTable()) {
lightingTable = cache.getWorld().getBrightnessTable();
}
else {
lightingTable = null;
}
}
/**
* Get our shader
*/
@Override
public HDShader getShader() {
return TexturePackHDShader.this;
}
/**
* Get our map
*/
@Override
public HDMap getMap() {
return map;
}
/**
* Get our lighting
*/
@Override
public HDLighting getLighting() {
return lighting;
}
/**
* Reset renderer state for new ray
*/
@Override
public void reset(HDPerspectiveState ps) {
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
setLastBlockState(DynmapBlockState.AIR);
lastblkhit = DynmapBlockState.AIR;
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
@Override
public boolean processBlock(HDPerspectiveState ps) {
DynmapBlockState blocktype = ps.getBlockState();
if ((hiddenids != null) && hiddenids.get(blocktype.globalStateIndex)) {
blocktype = DynmapBlockState.AIR;
}
if (blocktype.isAir()) {
lastblkhit = blocktype;
return false;
}
DynmapBlockState lastblocktype = lastblk;
/* Get color from textures */
if (scaledtp != null) {
scaledtp.readColor(ps, mapiter, c, blocktype, lastblocktype, ShaderState.this);
}
lastblkhit = blocktype;
if (c.getAlpha() > 0) {
/* Scale brightness depending upon face */
if (ps.getShade()) {
if (this.lightingTable != null) {
switch (ps.getLastBlockStep()) {
case X_MINUS:
case X_PLUS:
/* 60% brightness */
c.blendColor(0xFF999999);
break;
case Y_MINUS:
// 95% for even
if((mapiter.getY() & 0x01) == 0) {
c.blendColor(0xFFF3F3F3);
}
break;
case Y_PLUS:
/* 50%*/
c.blendColor(0xFF808080);
break;
case Z_MINUS:
case Z_PLUS:
default:
/* 80%*/
c.blendColor(0xFFCDCDCD);
break;
}
}
else {
switch (ps.getLastBlockStep()) {
case X_MINUS:
case X_PLUS:
/* 60% brightness */
c.blendColor(0xFFA0A0A0);
break;
case Y_MINUS:
case Y_PLUS:
/* 85% brightness for even, 90% for even*/
if((mapiter.getY() & 0x01) == 0)
c.blendColor(0xFFD9D9D9);
else
c.blendColor(0xFFE6E6E6);
break;
default:
break;
}
}
}
/* Handle light level, if needed */
lighting.applyLighting(ps, this, c, tmpcolor);
/* If grid scale, add it */
if(gridscale > 0) {
int xx = mapiter.getX() % gridscale;
int zz = mapiter.getZ() % gridscale;
if(((xx == 0) && ((zz & 2) == 0)) || ((zz == 0) && ((xx & 2) == 0))) {
for(int i = 0; i < tmpcolor.length; i++) {
int v = tmpcolor[i].getARGB();
tmpcolor[i].setARGB((v & 0xFF000000) | ((v & 0xFEFEFE) >> 1) | 0x808080);
}
}
}
/* If no previous color contribution, use new color */
if(color[0].isTransparent()) {
for(int i = 0; i < color.length; i++)
color[i].setColor(tmpcolor[i]);
return (tmpcolor[0].getAlpha() == 255);
}
/* Else, blend and generate new alpha */
else {
int alpha = color[0].getAlpha();
int alpha2 = tmpcolor[0].getAlpha() * (255-alpha) / 255;
int talpha = alpha + alpha2;
if(talpha > 0)
for(int i = 0; i < color.length; i++) {
int tc = tmpcolor[i].getARGB();
int cc = color[i].getARGB();
color[i].setARGB((talpha << 24)
| (((((tc >> 16) & 0xFF) * alpha2 + ((cc >> 16) & 0xFF) * alpha) / talpha) << 16)
| (((((tc >> 8) & 0xFF) * alpha2 + ((cc >> 8) & 0xFF) * alpha) / talpha) << 8)
| ((( tc & 0xFF) * alpha2 + ( cc & 0xFF) * alpha) / talpha));
}
else
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
return (talpha >= 254); /* If only one short, no meaningful contribution left */
}
}
return false;
}
/**
* Ray ended - used to report that ray has exited map (called if renderer has not reported complete)
*/
@Override
public void rayFinished(HDPerspectiveState ps) {
}
/**
* Get result color - get resulting color for ray
* @param c - object to store color value in
* @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer
*/
@Override
public void getRayColor(Color c, int index) {
c.setColor(color[index]);
}
/**
* Clean up state object - called after last ray completed
*/
@Override
public void cleanup() {
if (ctm_cache != null) {
ctm_cache.clear();
ctm_cache = null;
}
}
@Override
public DynLongHashMap getCTMTextureCache() {
if (ctm_cache == null) {
ctm_cache = new DynLongHashMap();
}
return ctm_cache;
}
@Override
public int[] getLightingTable() {
return lightingTable;
}
@Override
public void setLastBlockState(DynmapBlockState new_lastbs) {
lastblk = new_lastbs;
}
// Return last blockc with surface hit
public DynmapBlockState getLastBlockHit() {
return lastblkhit;
}
}
/**
* Get renderer state object for use rendering a tile
* @param map - map being rendered
* @param cache - chunk cache containing data for tile to be rendered
* @param mapiter - iterator used when traversing rays in tile
* @param scale - scale of perspective
* @return state object to use for all rays in tile
*/
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new ShaderState(mapiter, map, cache, scale);
}
/* Add shader's contributions to JSON for map object */
@Override
public void addClientConfiguration(JSONObject mapObject) {
s(mapObject, "shader", name);
}
@Override
public void exportAsMaterialLibrary(DynmapCommandSender sender, OBJExport out) throws IOException {
if (tp == null) {
getTexturePack(); // Make sure its loaded
}
if (tp != null) {
tp.exportAsOBJMaterialLibrary(out, out.getBaseName());
return;
}
throw new IOException("Export unsupported - invalid texture pack");
}
@Override
public String[] getCurrentBlockMaterials(DynmapBlockState blk, MapIterator mapiter, int[] txtidx, BlockStep[] steps) {
if (tp == null) {
getTexturePack(); // Make sure its loaded
}
if (tp != null) {
return tp.getCurrentBlockMaterials(blk, mapiter, txtidx, steps);
}
return new String[txtidx.length];
}
}

View file

@ -0,0 +1,47 @@
package org.dynmap.hdmap;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapCore;
import org.dynmap.renderer.DynmapBlockState;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.MapIterator;
public class TexturePackHDUnderwaterShader extends TexturePackHDShader {
private boolean hide_land = true;
class UnderwaterShaderState extends TexturePackHDShader.ShaderState {
private boolean ready;
private DynmapBlockState full_water;
protected UnderwaterShaderState(MapIterator mapiter, HDMap map, MapChunkCache cache, int scale) {
super(mapiter, map, cache, scale);
full_water = DynmapBlockState.getBaseStateByName(DynmapBlockState.WATER_BLOCK);
}
@Override
public void reset(HDPerspectiveState ps) {
super.reset(ps);
ready = (!hide_land); // Start ready if not hiding land
}
/**
* Process next ray step - called for each block on route
* @return true if ray is done, false if ray needs to continue
*/
public boolean processBlock(HDPerspectiveState ps) {
DynmapBlockState bs = ps.getBlockState();
if (bs.isWater() || bs.isWaterlogged()) {
ready = true;
this.lastblk = full_water;
this.lastblkhit = full_water;
}
return ready ? super.processBlock(ps) : false;
}
}
public TexturePackHDUnderwaterShader(DynmapCore core, ConfigurationNode configuration) {
super(core, configuration);
hide_land = configuration.getBoolean("hide-land", true);
}
@Override
public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter, int scale) {
return new UnderwaterShaderState(mapiter, map, cache, scale);
}
}

View file

@ -0,0 +1,172 @@
package org.dynmap.hdmap;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.dynmap.DynmapCore;
import org.dynmap.Log;
import org.dynmap.common.DynmapServerInterface;
public class TexturePackLoader {
private ZipFile zf;
private File tpdir;
private DynmapServerInterface dsi;
private static final String RESOURCEPATH = "texturepacks/standard";
private static class ModSource {
ZipFile zf;
File directory;
}
private HashMap<String, ModSource> src_by_mod = new HashMap<String, ModSource>();
public TexturePackLoader(File tp, DynmapCore core) {
if (tp.isFile() && tp.canRead()) {
try {
zf = new ZipFile(tp);
} catch (IOException e) {
Log.severe("Error opening texture pack - " + tp.getPath());
}
}
else if (tp.isDirectory() && tp.canRead()) {
tpdir = tp;
}
else {
Log.info("Texture pack not found - " + tp.getPath());
}
dsi = core.getServer();
}
public InputStream openTPResource(String rname, String altname) {
InputStream is = openTPResource(rname);
if (is == null) {
if (altname != null) {
is = openTPResource(altname);
}
}
return is;
}
public InputStream openTPResource(String rname) {
return openModTPResource(rname, null);
}
public InputStream openModTPResource(String rname, String modname) {
try {
if (zf != null) {
ZipEntry ze = zf.getEntry(rname);
if ((ze != null) && (!ze.isDirectory())) {
return new BufferedInputStream(zf.getInputStream(ze));
}
}
else if (tpdir != null) {
File f = new File(tpdir, rname);
if (f.isFile() && f.canRead()) {
return new BufferedInputStream(new FileInputStream(f));
}
}
} catch (IOException iox) {
}
// Fall through - load as resource from mod, if possible, or from jar
InputStream is = dsi.openResource(modname, rname);
if (is != null) {
return new BufferedInputStream(is);
}
if (modname != null) {
ModSource ms = src_by_mod.get(modname);
if (ms == null) {
File f = dsi.getModContainerFile(modname);
ms = new ModSource();
if (f != null) {
if (f.isFile()) {
try {
ms.zf = new ZipFile(f);
} catch (IOException e) {
}
}
else {
ms.directory = f;
}
}
src_by_mod.put(modname, ms);
}
try {
if (ms.zf != null) {
ZipEntry ze = ms.zf.getEntry(rname);
if ((ze != null) && (!ze.isDirectory())) {
is = ms.zf.getInputStream(ze);
}
}
else if (ms.directory != null) {
File f = new File(ms.directory, rname);
if (f.isFile() && f.canRead()) {
is = new FileInputStream(f);
}
}
} catch (IOException iox) {
}
}
if (is == null) {
is = getClass().getClassLoader().getResourceAsStream(RESOURCEPATH + "/" + rname);
}
if ((is == null) && (modname != null)) {
Log.warning("Resource " + rname + " for mod " + modname + " not found");
}
return (is != null) ? new BufferedInputStream(is) : null;
}
public void close() {
if(zf != null) {
try { zf.close(); } catch (IOException iox) {}
zf = null;
}
for (ModSource ms : src_by_mod.values()) {
if (ms.zf != null) {
try { ms.zf.close(); } catch (IOException iox) {}
}
}
src_by_mod.clear();
}
public void closeResource(InputStream is) {
try {
if (is != null)
is.close();
} catch (IOException iox) {
}
}
public Set<String> getEntries() {
HashSet<String> rslt = new HashSet<String>();
if (zf != null) {
Enumeration<? extends ZipEntry> lst = zf.entries();
while(lst.hasMoreElements()) {
rslt.add(lst.nextElement().getName());
}
}
if (tpdir != null) {
addFiles(rslt, tpdir, "");
}
return rslt;
}
private void addFiles(HashSet<String> files, File dir, String path) {
File[] listfiles = dir.listFiles();
if(listfiles == null) return;
for(File f : listfiles) {
String fn = f.getName();
if(fn.equals(".") || (fn.equals(".."))) continue;
if(f.isFile()) {
files.add(path + "/" + fn);
}
else if(f.isDirectory()) {
addFiles(files, f, path + "/" + f.getName());
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more