Compare commits

..
3930 Commits
Author SHA1 Message Date
Brian Neumann-Fopiano b18681a3e2 Command Corrections. 2026-09-17 04:48:35 -04:00
Brian Neumann-Fopiano 1e1eaa96aa Hydrology Fix, and V+ 2026-09-15 23:01:56 -04:00
Brian Neumann-Fopiano dfb3748059 Adapter and papi fixes 2026-09-13 21:27:44 -04:00
Brian Neumann-Fopiano c14a972ab8 Fixed Maven publishing
- ItemsAdder API resolves from Maven Central
- Integration repositories only resolve their assigned groups and modules
- Shared VolmLib classes and packaging use the same pinned revision
- JitPack builds and publishes with remote dependencies
- Local Maven publication uses art.arcane:iris with the configured version
- API generation tracks the final archive filename lazily
- API jars retain signatures and metadata with valid throwing method stubs
- Publication tests verify metadata, bytecode, and consumer compilation
2026-09-13 17:05:05 -04:00
Brian Neumann-Fopiano 9a1e4b91d1 Generation and runtime fixes
- Surface detail blends raw noise with cached six-block interpolation
- Locked biome layers sample only the requested ranges
- Surface layers skip noise when slope requirements fail
- Stilt supports exclude configured materials and block entities
- Ice and glass remain eligible foundation materials
- Block-drop routing filters materials before loading biome history
- Vision displays biome previews before river refinement
- Interrupted previews retain reusable partial tiles
- Hydrology waiters stop without cancelling shared planning tasks
- Regression tests cover generation, supports, drop routing, and preview cancellation
2026-09-13 17:00:33 -04:00
Brian Neumann-Fopiano e3c6a32968 Propere types 2026-09-13 12:53:43 -04:00
Brian Neumann-Fopiano ef26f3f443 Correcting merge conflit 2026-09-12 17:57:24 -04:00
Brian Neumann-Fopiano 7a099cbd5e Merge Back 2026-09-12 16:06:08 -04:00
Robert Stoneking e832239730 Fixed language reload fallback
- Invalid language updates retain the last valid messages
- Malformed startup catalogs use English defaults
- Unrelated reloads preserve personal language caches
- Deleted locales use English without changing saved choices
- Restored locale files resume their translations
- Language catalogs include shared editor titles and remove obsolete usage messages
2026-09-12 13:53:11 -06:00
Brian Neumann-Fopiano bc9b7e86d7 Merge debug command reorganization
- Swift's debug command tree lands on the consolidated package layout
- CommandDebug and its tests move from core.commands to command
2026-09-11 21:47:30 -04:00
Brian Neumann-Fopiano 3f57925443 Consolidate source layout
- Packages regrouped by domain: generation, world, pack, studio, structure, platform, command, localization, diagnostics
- Forked VolmLib utilities dropped for the shared library: noise, stream, interpolation, hunk, matter
- Engine-coupled stream decorators moved to generation.stream.GenerationStreams
- Hunk section splits now run on the Iris burst pool through HunkBurst
- Artifact entry lists, purity allowlists and shrink rules follow the new paths
2026-09-11 21:46:22 -04:00
Robert Stoneking 251b0e250d Reorganized debug commands
- Debug dump replaces the root debugdump command
- Debug toggle controls diagnostic logging
- Debug version uses the shared version display
- Root version remains executable but hidden from help
- Diagnostic dump routing retains its separate permission path
- Translations and tests cover the revised command structure
2026-09-11 17:21:04 -06:00
Brian Neumann-Fopiano 29a31a69e0 Patching Cleanup 2026-09-11 00:38:08 -04:00
Brian Neumann-Fopiano bf12f30d26 Hydrology fixes 2026-09-10 23:07:46 -04:00
Brian Neumann-Fopiano 577f924133 Merge remote-tracking branch 'origin/master' 2026-09-10 13:08:14 -04:00
Brian Neumann-Fopiano e4cdf2dcda Patches 2026-09-10 13:08:12 -04:00
Robert Stoneking 5ba95388f8 Merge remote-tracking branch 'origin/master' 2026-09-10 01:17:34 -06:00
Robert Stoneking 45407a7e3f Updated Iris language files -
All 17 translations now use editable TOML files
Language menus and debug messages include translations
Headers explain all 243 variables, prefixes, colors and formatting
Generated guides refresh without overwriting customized messages
Missing or invalid messages fall back to English
Language reloads preserve server and player selections
Jars include localized guides and required TOML support
2026-09-10 01:17:18 -06:00
Brian Neumann-Fopiano e33e6a632a NBative Structure fixes.
- This fixes the native structures that are in iris from generating with pillaric problems
2026-09-10 02:15:20 -04:00
Brian Neumann-Fopiano 62e61970f8 Production-readiness suite fixes
Surface hydrology policy split, native terrain height cache, watched-edit
hotload cancellation, world-save requestSave with tracked ownership flush,
background-task drain that follows saves admitted mid-drain, peaceful spawn
guard, cached-completion terminal state, IO drains off the owner thread.

Studio pregen at scale: detached historical runtimes keep the configured
noise cache instead of the Studio-wide cache; retired runtimes are no longer
pinned by the volumetric fragment filter's per-thread scratch; pregen mantle
cleanup and native region flushes run on per-run threads instead of the
server thread and the shared Iris IO pool (the flush on the IO pool starved
tectonic plate loads and wedged every Paper worker in Mantle.getChunks);
background-task cancellation claims tasks atomically.
2026-09-09 16:24:05 -04:00
Brian Neumann-Fopiano 4de1aea8b9 CrantEngine Tweak and a bit more.
- Fixed CE support
- Added some checks for things like paint and so on in the 3d Generator.
2026-09-08 19:22:55 -04:00
Brian Neumann-Fopiano 0782bbf0b4 Whoops Core 2026-09-08 16:15:19 -04:00
Brian Neumann-Fopiano 98e51cba8d Merge master into 3D terrain
- Resolve IrisTerrainNormalActuator by keeping master's primitive riverWaterSurfaceStream lookup and primarySurfaceLayerOrNull accessors while retaining the terrain column, span, layer surface and ceiling locals that write air through terrain3D gaps
- Resolve HydrologyFootprintCompiler by keeping the MaterializedSurface planned-surface record with terrain ownership beside master's package-private SurfaceRasterIndex, which retains its ownsTerrain override
- Resolve HydrologyCaveContainmentPlanner by dropping the nested RecordingCaveVoxelView, SurfaceObservations and ViewObservations types that master promoted to their own files
- Port the surface-observation ownership check into CaveSurfaceObservations so cached cave validations replay both height and ownership observations through Observation.matches
- Resolve PackExportClosureTest by keeping master's retirement of the source-text packager guard while retaining the snippet export tests, and restore the Files and Path imports those tests use
- Resolve IrisBiomeLayerSlopeSourceTest by folding the lower-ledge slope palette case into master's lockLayers parameterization
- Recapture the studio schema digest for the terrain3D biome property, its IrisTerrain3D definition, the terrain-3d snippet enumeration and the reworded upperDimensionCarving description, verified by reconstructing the previous digest from the stripped schema
2026-09-08 13:58:00 -04:00
Brian Neumann-Fopiano c39a6403bc Volumetric surface terrain
- Add an optional per-biome `terrain3D` profile with density and fissure noise styles, amplitude, horizontal and vertical feature scales, crack depth, width and scale, slope and fluid-clearance gates, and a seed offset, each range-checked before use
- Add a terrain runtime that samples the signed density and fissure fields on a four-block lattice around the biome's generated height and resolves each column into solid spans, so surfaces gain projecting rock, undercuts, ledges and narrow cracks that fade out on flat ground and near the fluid line
- Remove unsupported rock by flood-filling span components across neighbouring columns and dropping any fragment of 512 blocks or fewer, while larger connected masses are kept
- Report the shaped column top as the natural terrain height, keep the unshaped generator height available as a separate stream, and measure surface slope between neighbouring ledge floors instead of the flat heightmap
- Write air through the gaps in the normal terrain actuator, generate a fresh surface palette for every ledge floor, and paint ceiling layers beneath undercuts
- Carry each layer's own column through the dimension stack and upper dimension so stack top heights, solidity and surface lookups follow the spans, and each exposed face draws its palette from its own source height and slope
- Sample a layer's slope condition once per column at the surface being generated, and gate surface decorators on the slope of the ledge they sit on rather than the column heightmap
- Decorate every exposed ledge floor and the ceiling above it during the decorant pass
- Treat shaped openings as surface rather than cave when resolving biomes, skip them during cave carving, suppress cave zone markers inside them, and resolve their boundary biome from the surface biome stream
- Include shaped openings in mantle carve queries and carved columns, fall back to terrain solidity for surface-solid checks across stack, upper and normal terrain, and answer both queries from the island mask when placing objects on floating islands
- Let the hydrology planned surface report whether it owns a column, so the cave voxel view consults natural terrain solidity where hydrology does not own the terrain, and split replayed plan observations into height and ownership records
- Validate `terrain3D` on biomes and `snippet/terrain-3d` snippets as a blocking pack error, rejecting unknown fields, wrong types, out-of-range numbers, unknown noise styles, style nesting past 32 levels and snippet references that escape the pack
- Copy the whole snippet tree during modded studio pack export through one shared routine instead of only `snippet/style`, so terrain and nested style snippets survive export
- Add a terrain probe and Gradle task that generates real pack chunks, compares predicted column geometry against the actual blocks, requires retained overhang coverage for named biomes, reports detached-component topology, and writes cross-section images with a JSON summary
- Enumerate image-mapped regions and skip compat-excluded ones when collecting biomes for stacked and upper dimension terrain
2026-09-08 13:26:33 -04:00
Brian Neumann-Fopiano 425d305d0b Drop JIT-bound allocation assertion
- Remove the accessor allocation comparison whose per-call byte counts depend on escape analysis timing and failed under load, keeping the three behavioral accessor tests
2026-09-08 06:36:17 -04:00
Brian Neumann-Fopiano 114cc68a08 Extend VolmLib interpolation
- Make IrisInterpolation extend the VolmLib IrisInterpolation and delete the nine numerically identical statics it duplicated: hermite, cubic, bicubic, bihermite, trihermite, tricubic, lerp, blerp and trilerp
- Keep the Iris rangeScale because the VolmLib copy short-circuits bmax == bmin and generation output must not move
- Ship the VolmLib IrisInterpolation and InterpolationMethod by removing their unusedVolmLibClasses entries
2026-09-08 06:32:06 -04:00
Brian Neumann-Fopiano 3551614088 Extend VolmLib storage hunks
- Make StorageHunk, ArrayHunk, AtomicHunk, SynchronizedArrayHunk, AtomicDoubleHunk, AtomicIntegerHunk and AtomicLongHunk extend their VolmLib counterparts and only bind the Iris Hunk interface
- Drop the Lombok data, equals and hashCode generation the duplicated storage hunks carried
- Ship the VolmLib storage hunks by removing their unusedVolmLibClasses entries
2026-09-08 06:32:06 -04:00
Brian Neumann-Fopiano 355d7f4132 Delete dead util duplicates
- Delete unused board BoardSettings and ScoreDirection wrappers around the VolmLib board types
- Delete unused inventorygui UIStaticDecorator, Window and WindowResolution wrappers around the VolmLib inventorygui types
- Delete unused VectorMath subclass of the VolmLib VectorMath
- Delete unused InterpolationType enum and the IrisInterpolation blerp overload that keyed on it
- Delete HunkFace and point Hunk.viewFace and Hunk.cropFace at the VolmLib HunkFace
- Drop the iris board and inventorygui excludes from supersededVolmLibPackages and the VectorMath entry from unusedVolmLibClasses
- Drop the deleted inventorygui files from the Bukkit purity allowlist
2026-09-08 06:32:06 -04:00
Brian Neumann-Fopiano 00474252b4 Drop stale artifact entry
- Remove VectorNoiseKernels2D from requiredBukkitArtifactEntries; the class was deleted with the unwired 2D kernels and verifyBukkitArtifact failed on every build
2026-09-08 06:32:06 -04:00
Brian Neumann-Fopiano cab35996f8 Fix identity digest separator
- Separate hashed studio project entries with a space instead of a null byte
- Repin the scripted session digest for the corrected separator
2026-09-08 06:20:54 -04:00
Brian Neumann-Fopiano 2451c89cee Split jigsaw studio service
- Move world registration and shutdown quiescing into JigsawStudioRegistry
- Move save, export and close arbitration into JigsawStudioSaveLifecycle
- Move autosave scheduling, drains and undo into JigsawStudioAutosaveScheduler
- Move variant creation, duplication and deletion into JigsawStudioVariantEditor
- Move variant metadata and resize edits into JigsawStudioVariantProperties
- Move workcell capacity, labels and connector blocks into JigsawStudioWorkcellEditor
- Move graph mutation and live relayout into JigsawStudioGraphMutations
- Move dynamic graph evaluation into JigsawStudioEvaluator
- Move menu state, studio tools and deferred duplication into JigsawStudioToolbelt
- Move the studio world event handlers into JigsawStudioProtectionListener
- Move ownership and mutation guards into JigsawStudioProtection
- Move variant materialization and hydration into JigsawStudioMaterializer
- Move dirty marking into JigsawStudioDirtyTracker
- Move workcell capture scheduling into JigsawStudioCaptureScheduler
- Move chunk capture and connector aggregation into JigsawStudioCapture
- Move materialized and rendered chunk writing into JigsawStudioChunkWriter
- Move jigsaw marker NBT watching into JigsawStudioTileWatcher
- Move particle preview rendering into JigsawStudioVisualization
- Move player workcell context into JigsawStudioPlayerContext
- Point the studio service tests at the relocated collaborators
- Record the split studio sources in the Bukkit purity allowlist
2026-09-08 06:20:54 -04:00
Brian Neumann-Fopiano 8b9d1fb2fb Split jigsaw studio menus
- Move each jigsaw menu kind into its own renderer class
- Move workcell capacity staging into JigsawStudioWorkcellMenu
- Move menu presentation helpers and adjustment math into JigsawStudioMenuFormat
- Promote the menu action contract to JigsawStudioMenuActions
- Point the menu controller test at the relocated menu types
2026-09-08 06:20:54 -04:00
Brian Neumann-Fopiano e7c6b6e08e Split jigsaw graph editor
- Move planar object resize geometry into JigsawStudioObjectResizer
- Move owned graph JSON document transforms into JigsawStudioGraphDocuments
- Move owned graph reference checks into JigsawStudioGraphInspector
- Point the graph editor resize test at the relocated resizer
- Add a scripted studio persistence session digest test over project and history bytes
2026-09-08 06:20:54 -04:00
Brian Neumann-Fopiano a4e22006e0 Split jigsaw command tree
- Move each jigsaw subgroup executor into its own top-level command class
- Move the adoption plan store, handlers and helpers into CommandJigsawAdopt
- Move the vanilla export lease and reporting helpers into CommandJigsawExports
- Move the studio open lifecycle into CommandJigsawOpen
- Point the jigsaw contract test at the relocated command types
- Exclude the new jigsaw command sources from the generation revision scope
2026-09-08 06:20:54 -04:00
Brian Neumann-Fopiano 85e12caf9b Split hydrology footprint and cave planners
- Move segment rasterisation and channel shaping out of HydrologyFootprintCompiler into HydrologyFootprintRasterizer and HydrologyChannelGeometry
- Move chamber, throat and pool construction out of HydrologyCaveContainmentPlanner into HydrologyCaveChamberPlanner
- Promote the candidate span, candidate builder, stencil, column and cave observation types out of the three compilers to top level
2026-09-08 05:57:18 -04:00
Brian Neumann-Fopiano 2fb1dc4fd6 Restore datapack bukkit purity
- Return the Minecraft version lookups to DatapackIngestService so no new file couples to org.bukkit
- Drop the redundant self imports of the service's own nested types
2026-09-08 05:57:18 -04:00
Brian Neumann-Fopiano a31d494f45 Split hydrology tile planner
- Keep planner state, caches, cross-tile entry points and terrain sampling in HydrologyPlanner and move tile planning into ten collaborators for cross-tile resolution, outlets, sources, route geometry, route paths, surface courses, tributaries, underground courses, hydraulic segments and feature sites
- Promote the shared planning records to top level so the collaborators address routes, grids, drafts and course paths directly
- Pin a full four-tile plan digest for two seeds in HydrologyPlanHashTest
- Requalify the reflective and package-private planner call sites in the hydrology tests onto the new owners
2026-09-08 05:57:18 -04:00
Brian Neumann-Fopiano 422618282e Split datapack ingest service
- Keep ingest orchestration and the public entry points in DatapackIngestService and move startup validation, staging reapply, removal, install planning, ownership, manifests, transactions and recovery into dedicated package classes
- Promote the shared ingest data types to top level so every collaborator addresses them directly
- Report the managed metadata IO failures on the reapply path instead of logging them at debug
- Requalify DatapackIngestServiceTest call sites onto the new owners without changing any test
2026-09-08 05:57:18 -04:00
Brian Neumann-Fopiano 9775a50b26 Split schema property builder
- Move registry catalogs, enum definitions and block-state groups out of SchemaBuilder into SchemaRegistryCatalog, SchemaEnums and SchemaBlockStates
- Decompose buildProperty into SchemaPropertyBuilder per-type handlers with SchemaRegistryProperties owning the per-annotation registry chains
- Report the font enumeration, registry list function and default value probe failures instead of swallowing them
- Pin the generated studio schema digest for fifteen pack roots in SchemaBuilderIdentityTest
2026-09-08 05:57:18 -04:00
Brian Neumann-Fopiano 290412b62a Cover untested contract surfaces
- Add a test source set to spi with bind, service registry and platform value contracts
- Prove the DataType probe contract, the ExternalDataProvider defaults and the string-only provider namespace claims
- Prove Identifier parsing and rendering
- Cover the data version, primitive and vector director handlers
- Cover the vanilla export identifier, block state, orientation, request and settings values
- Cover the structure adoption limits, diagnostics and result invariants
- Cover the pregen mantle backpressure, heap reclaim urgency, empty cache, listener defaults and diagnostic descriptions
2026-09-08 05:27:49 -04:00
Brian Neumann-Fopiano 71e3af727f Parameterize template-identical tests
- Split PackValidatorCustomBiomeSpawnTest into parameterized accepted and rejected suites plus the resolver-failure case
- Drive IrisCarveWallPaletteScopeTest from its four generation-scope modes
- Parameterize the legacy bilerp parity samples in IrisComplexGridBoundsCacheTest behind Enclosed
- Parameterize the resolved bounds in IrisStructureYBandTest
- Parameterize the entity key normalization in IrisBiomeCustomSpawnTest
- Parameterize the platform identity in IrisWorldIdentityTest
- Parameterize the layer lock in IrisBiomeLayerSlopeSourceTest
2026-09-08 05:27:49 -04:00
Brian Neumann-Fopiano c3bc0f5d18 Split the router fork floor
- Replace GenerationHistoryRuntimeRouterTest with promotion, routing and lifecycle classes
- Add GenerationHistoryRuntimeRouterSupport on top of GenerationHistorySupport for the shared fakes and helpers
- Drop the router-local copies of createPack, createHistory, stage, contract, signature and writeRegion
2026-09-08 05:27:49 -04:00
Brian Neumann-Fopiano 9dcaed5315 Share test fixtures across modules
- Move art.arcane.iris.testsupport into core/src/testFixtures and apply java-test-fixtures to core
- Consume core test fixtures from the bukkit plugin, nms, probe and the three loader adapters
- Delete the duplicate ProjectPaths copies in the bukkit-plugin and modded-common test trees
- Point ModdedMessageContributorTest at the shared ProjectPaths
- Guard CommandPackCompatTest, NativeTransitionColumnTest and StubPlatformStateTest with PlatformLeakGuard
- Release the probe runtime state after DimensionStackSuperflatProbeTest opens a workspace
- Keep core main classes ahead of the core jar on the test classpath so the bytecode gates still scan a directory
2026-09-08 05:27:49 -04:00
Brian Neumann-Fopiano fd146de1ad Drop added code comments
- Remove the Javadoc and inline comment blocks the three runtime-safety commits added under src/main
- Keep the GPL file header on the new spi type, matching every other file in that module
- Leave the superseded stale-temp ordering comment deleted rather than restoring text the new boot path contradicts
2026-09-08 04:48:43 -04:00
Brian Neumann-Fopiano 486c6eeae9 Defer runtime injection
- Add RuntimeInjection so the Java agent attach and the server-code injection run once, on demand, instead of during every enable
- Install the injection when a world Iris created is about to be built and when Bukkit asks for the generator of a world configured outside Iris
- Lock the runtime with the same reason a boot-time injection failure used to raise when the deferred install fails
- Add general.eagerRuntimeInjection to keep boot-time injection and its startup verification
- Re-read the startup lock after a deferred install so a failed install refuses the world instead of generating it
- Move the stale cache/temp sweep off the boot thread and make every pack import wait it out
- Move the default-pack presence warning off the boot thread; it walks the whole pack tree to pick a warning
- Build the wand and dust templates on the first tick instead of during service enable
- Report enable duration, per-phase timings and the three slowest services on every boot
- Drop the wand and dust templates on service disable
2026-09-08 04:45:55 -04:00
Brian Neumann-Fopiano 6c6dba0232 Release runtime statics
- Null Iris.instance, the console sender, the Multiverse link, the compat set and the chunk tickets at the end of teardownRuntime
- Bind the host bridge suppliers to the constructing plugin instead of reading the instance static on every call
- State that Iris is disabled when service, getTemp or getSender is reached after teardown instead of raising a bare NullPointerException
- Add IrisRuntimeStatics so a disable drops the service registry, both capability singletons, the loaded datapack runtime and the localization roots
- Add WorldLifecycleService.reset and WorldRuntimeControlService.reset so the next boot snapshots the server it is running on
- Add ServerConfigurator.resetLoadedDatapackRuntime so an enable revalidates instead of trusting the previous boot's fingerprint
- Clear the localization data folder and plain-text memo on shutdown
- Add BukkitPlatform.releaseHost and call it last in terminal cleanup so the disabled plugin, console sender and log bridge stop being reachable from a static
2026-09-08 04:44:32 -04:00
Brian Neumann-Fopiano f83cf71b11 Report swallowed failures
- Add art.arcane.iris.spi.CapabilityProbe so an absent optional API is recorded at debug with the failure class and message instead of a bare ignored catch
- Route runtime day-time reads, clock pushes and the noon time lock through IrisLogging.reportError with the world name and the full trace
- Report a level storage access that will not close, so a retained world session lock names its cause
- Report a failed iris.json rewrite with the file path instead of dropping the IOException
- Report Iris world registry and dimension read failures during Multiverse reconciliation instead of logging one debug line
- Report a failed pack compatibility summary and a failed vanilla-slot capture with their traces
- Report a superseded generation semantics shard that cannot be deleted, once, with its path
- Restore the interrupt flag when the global cache trimmer join is interrupted
- Replace ValueWithDiagnostics with CheckResult so a startup check carries the runtime lock reason it raises
- Give every startup check an explicit advisory or critical severity and lock the runtime on any Danger result, replacing the catch that recognised the injection check by id
- Lock the runtime when general.disableNMS holds a no-op binding or the NMS binding is unbound, instead of marking the runtime ready
- Match incompatible plugin names against every registered plugin without regard to case
- Name the JVM maximum heap in the memory check output
- Print the lock reason and what it blocks when Iris enables into a locked runtime
- Carry the per-check safeguard modes and diagnostic lines in /iris debugdump
2026-09-08 04:44:31 -04:00
Brian Neumann-Fopiano 45b9ff8e11 Drop unwired 2D noise kernels
- Delete NoiseKernels2D, ScalarNoiseKernels2D and VectorNoiseKernels2D after measuring the vector kernel at 0.07x scalar on 2-lane NEON with no production call site
- Delete the 2D noise parity test and the SimdSupport plumbing that selected between the two kernel sets
- State in the simd-bench README that its 2D noise classes are a candidate Iris does not ship
2026-09-08 04:33:32 -04:00
Brian Neumann-Fopiano 79af11c435 Trim generation hot path allocations
- Replace the six per-voxel IrisPosition probes in MantleWriter neighborhood hollowing with an origin-relative primitive key set
- Look mask membership up through the same primitive key set instead of allocating an IrisPosition per candidate voxel
- Give the squared-distance helper a fixed arity so ballooning no longer depends on escape analysis to drop its varargs array
- Resolve each Matter slice once per write instead of paying a hasSlice probe and a getSlice lookup for the same class
- Read the river water surface through the primitive stream accessor instead of boxing a Double per column
- Add nullable primary surface layer accessors so the terrain actuator stops building two Optionals per column
- Add MantleWriterNeighborhoodTest, MantleWriterNeighborhoodBenchmarkTest and HydrologyColumnSampleAccessorTest
2026-09-08 04:33:32 -04:00
Brian Neumann-Fopiano 1be563bd28 Unblock pregen throughput limiters
- Replace the pregen cache global monitor with atomic per-plate and per-region state and a lock-free resident-plate memo
- Serialize plate eviction and its disk write under the residency lock so a reloaded plate can never lose a concurrent bit
- Retry a chunk or region cache write whose plate was retired mid-write
- Delete SynchronizedCache and the isThreadSafe wrapper it existed for
- Replace the AsyncPregenMethod semaphore and permit monitor with PregenAdmissionGate, one condition that signals on release, adaptive-limit change and cancellation
- Attribute the adaptive and permit waits separately from the single gate wait
- Wait on a progress signal instead of sleeping 25ms in PregenMantleBackpressure, signalled by chunk completion
- Wait on a state signal instead of sleeping 50ms and 150ms in IrisPregenerator pause, heap pressure and tectonic reclaim loops
- Restore the burst pool parallelism when the last pregen job finishes
- Add PregenCacheConcurrencyTest, PregenAdmissionGateTest and PregenWaitPolicyContractTest
2026-09-08 04:33:31 -04:00
Brian Neumann-Fopiano e030ac3be5 Report silent pregen failures
- Add PregenDiagnostics.probeFailed so capability probes log their name, class and message at debug instead of vanishing
- Report the moonrise worker pool, mantle radius, engine access and Folia submit probe failures through that helper
- Report async pregen mantle cleanup failures through IrisLogging.reportError with the trace instead of dropping them
- Report a stranded plugin chunk ticket through IrisLogging.reportError so the leaked pin is visible
- Emit the chunk load failure trace before the metrics snapshot so a failing snapshot can no longer silence it
- Report the pregen cancellation and mantle handle probe failures in PregenMantleBackpressure
- Report the Medieval engine resolution probe failure
- Record the real Vector API initialization failure in SimdSupport and print the accurate kernel selection reason
- Add PregenDiagnosticsTest, PregenSilentFailureContractTest and SimdSupportStatusTest
2026-09-08 04:33:31 -04:00
Brian Neumann-Fopiano 9d22a18a3a Close route off the caller thread
- Complete the async route close on the worker executor so the cross-thread assertion no longer depends on whether the first stage finished before the handler was attached
2026-09-08 04:28:40 -04:00
Brian Neumann-Fopiano e5b7cb97a8 Anchor repository root probe
- Require the core source tree alongside the settings file when walking up for the repository root, so modded-common tests built inside a loader adapter no longer stop at that adapter's own settings file
2026-09-08 04:28:40 -04:00
Brian Neumann-Fopiano bdb13d1647 Fix cross-thread report assertion
- Assert the spawn failure report through the bound platform instead of a thread-confined static mock, so the report is seen whichever thread wins the reporting race
- Guard WorldEntitySpawnerLifecycleTest against a leaked platform binding
- Unbind the stub platform after the probe module's platform state test
2026-09-08 04:28:17 -04:00
Brian Neumann-Fopiano 42798c6340 Poll instead of sleeping
- Add the Await test helper that polls a condition every ten milliseconds under a bounded timeout
- Replace every Thread.sleep in the fourteen polling test classes with a condition wait, a latch wait or a bounded quiet window
- Keep the crash-consistency fsync interception in GenerationHistoryAdmissionTest and SavedBiomeStoreBatchTest on real barriers
2026-09-08 04:28:17 -04:00
Brian Neumann-Fopiano c75b0e01af Resolve test paths from code source
- Add the ProjectPaths test helper that derives the module root from the test class code source and the repository root from the settings file above it
- Replace the working-directory-relative source and resource literals in thirty-five test classes with module and repository lookups
- Drop the bespoke source-root walk in PregenMantleCleanupContractTest
2026-09-08 04:26:33 -04:00
Brian Neumann-Fopiano 03c645b66f Isolate platform binding
- Add the PlatformBinding rule that binds a mock platform for a class or method, unbinds on teardown and restores the previous binding
- Add the PlatformLeakGuard rule that fails a class when a platform is still bound or a service is still registered at class start or end
- Add IrisRuntimeState so the guard reads the registered services without adding a test-only method to the SPI
- Replace the inline mock-platform setup and teardown of twenty test classes with the rule
- Guard the remaining sixty-four platform and service touching core test classes
- Rename the pack-compat pool fixtures to PackCompatFixtures so the two CompatFixtures names cannot collide
- Scope BukkitTestServer branding to a ServerBranding rule that restores the shared mock after each test
2026-09-08 04:22:49 -04:00
Brian Neumann-Fopiano c332c86849 Relax test fsync
- Add Durability, a process-wide fsync policy defaulting to real barriers and switchable to relaxed through the iris.durability system property
- Route the history, studio, journal, structure and datapack fsync barriers through Durability so relaxed mode skips both the force and the channel open
- Add the DurabilityMode test rule and enable relaxed durability on the twenty slowest core stores tests
- Split GenerationHistoryTest into epoch, legacy adoption and recovery classes over a shared GenerationHistorySupport base
2026-09-08 04:18:01 -04:00
Brian Neumann-Fopiano c75cfd387e Retire source-text tests
- Delete 22 test classes whose assertions only read production .java text and check call ordering or literal presence
- Remove 77 source-text assertion methods from 34 test classes that keep real behavioural coverage
- Replace the core process-stream source grep with an ASM gate over compiled core classes in CoreProcessStreamPurityGateTest
- Replace the packs-root source grep in PacksFolderContractTest with an ASM call-site gate over compiled core classes
- Replace the IrisData registered-root regex in PackTypeBukkitPurityGateTest with an ASM scan of the registerLoader class constants
- Trim six mixed tests down to their behavioural assertions and drop their source-text tails
- Drop the eight iris.*Source and three iris.moddedCommonSources test system properties now that nothing reads them
2026-09-08 04:01:33 -04:00
Brian Neumann-Fopiano 7841efed54 Run modded and buildSrc tests
- Add a root moddedTest that runs adapters/modded-common through the Fabric build
- Make check depend on moddedTest, ordered ahead of the loader jars
- Gate the buildSrc jar on the buildSrc tests so they stop being skipped
- Drop an unreachable classpath guard from SchemaBuilderPlatformIsolationTest
2026-09-08 03:41:15 -04:00
Brian Neumann-Fopiano 89dc5d0447 Drop execution-time project access
- Delete stale jars through an injected FileSystemOperations in the root build
- Delete stale loader jars through an injected FileSystemOperations in each adapter
- Resolve stale-jar file trees and copy sources at configuration time
- Expand the loader metadata templates from the configured version string
2026-09-08 03:41:03 -04:00
Brian Neumann-Fopiano 5c84c6b8d8 Derive modded bukkit baseline
- Read the modded bukkit-supertype baseline from core/purity-allowlist.txt
- Match nested classes against their top-level source entry
- Exempt VolmLib classes, which Iris' core ratchet does not govern
- Delete the hand-frozen knownBukkitSupertypeClasses inventory
2026-09-08 03:40:51 -04:00
Brian Neumann-Fopiano 0bdacc6fed Restore modded jar incrementality
- Drop outputs.upToDateWhen and outputs.cacheIf from the loader jar Exec tasks
- Declare lombok.config, core/agent and both revision scripts as modded inputs
2026-09-08 03:40:40 -04:00
Brian Neumann-Fopiano 6e3e1f3c64 Single relocation declaration
- Add gradle/runtime-relocations.gradle as the one runtime-download relocation map
- Read the slimJar relocations and shrinkRelocations from that map
- Pass the derived relocation prefixes into BukkitArtifactVerifier
- Drop the duplicate jar size check the packaging audit already enforces
2026-09-08 03:40:29 -04:00
Brian Neumann-Fopiano c83d6964f1 Gate mavenLocal behind property
- Stop mavenLocal shadowing every remote repository in allprojects
- Add -PuseMavenLocal, on by default only when the composite VolmLib is disabled
2026-09-08 03:40:15 -04:00
Brian Neumann-Fopiano b225c0b5be Ratchet net.minecraft purity
- Add minecraftPurityRatchet over core/minecraft-purity-allowlist.txt, wired into check
- Register both purity ratchets from one closure instead of duplicating the scan
- Prune WorldCreationProgressReporter from core/purity-allowlist.txt
2026-09-08 03:39:43 -04:00
Brian Neumann-Fopiano 1913baed17 Baseline Oraxen provider
- Add the Bukkit-only Oraxen data provider to the known Bukkit-supertype set so the modded artifact verifiers accept it like the ItemsAdder provider
2026-09-08 03:02:23 -04:00
Brian Neumann-Fopiano 031d4ade2c Wire Iris jar shrink
- Wire the ProGuard shrink pass into the Bukkit artifact with the core, Bukkit plugin and NMS compile classpaths as library jars
- Map the fourteen SlimJar relocations so runtime-downloaded libraries resolve under their relocated names
- Add packaging/shrink.pro keeping the public API, Iris events, platform SPI, VolmLib integration contract and Bukkit event handler lists
- Lower maximumBukkitArtifactBytes to 7,600,000 so verifyBukkitArtifact enforces the same budget as the packaging gate
- Build VolmitSender.name() from the sender name instead of the relocated CommandSender.name() reference, which cannot resolve on Paper 26.2
2026-09-07 23:28:48 -04:00
Brian Neumann-Fopiano 9b9ca8de34 Unlatch platform block states
- Replaced the nine static final PlatformBlockState fields in IrisDimensionStackActuator, IrisTerrainNormalActuator, IrisPerfectionModifier, IrisPostModifier, IrisBiomeLayerGenerator, BiomeBuffetGenerator, ChunkDataHunkHolder and EngineMantle with BoundBlockState holders that re-resolve when a different platform binds, costing one volatile read of the cached binding, one of the bound platform and a reference compare per read
- Turned the IrisPerfectionModifier ore-hiding table into a Map of BoundBlockState so the four base states follow the bound platform instead of the first one loaded
- Kept the last resolution in BoundBlockState while nothing is bound so in-flight generation on an unbinding server reads the same states it did before, matching what B.registries already does
- Added IrisPlatforms.getOrNull so the block-state holders read the binding once without an exception on a bare JVM
- Added PlatformStateRebindTest, which fails on the old code: it binds two mock platforms in turn and asserts ChunkDataHunkHolder and EngineMantle serve the second one, and rejects any static PlatformBlockState field left on the converted classes
- Indexed the .iob write palette by key so palette lookup is a hash probe rather than a scan of the palette list, cutting the 32767-state IrisObjectIoWriteLimitsTest from 4.9s to 0.7s while writing byte-identical files
- Built the palette once per write instead of once in validateWritable and again in the writer, and shared it between the plain and progress-reporting write paths
- Added IrisObjectIoPaletteTest, which pins the SHA-256 of a 30000-block, 3011-state object written by the previous implementation and asserts the palette keeps first-seen insertion order
- Added BukkitTestServer, a single mock Bukkit server installed once per JVM under a lock, stubbing logger, name, version, plugin manager and both createBlockData overloads, and failing loudly if the install does not take
- Moved IrisRuntimeSchedulerModeRoutingTest, WorldRuntimeControlServiceTimeLockTest, PreObjectMatterTest, CNGInjectorParityTest, BukkitSpiConformanceTest, MantleWriterPreObjectJournalTest, IrisFloatingChildBiomesCarvingResolutionTest and IrisDimensionCarvingResolverParityTest onto it, so whichever class runs first no longer decides whether createBlockData or getPluginManager is stubbed
- Extracted the KeyedBlockState test double out of IrisObjectIoWriteLimitsTest so the palette test can reuse it
- Replaced the IrisServices.clear() teardowns in nine test classes with a targeted remove of the PreservationRegistry each one registers, so a teardown no longer wipes a binding it did not install
- Registered a PreservationRegistry in PackDownloaderTest and StudioSVCWorldPackPublishTest, which reach IrisData.get and openRuntime and were silently getting loaderless IrisData whenever no other class had leaked one
2026-09-07 19:59:22 -04:00
Brian Neumann-Fopiano 266fe0d71c Faster green test suite
- Removed the core processResources skip that fired on test-only invocations, which packaged a core jar with no generation kernel catalog and made all 10 probe and bukkit-plugin tests that reach GenerationKernelRegistry fail on every ./gradlew test
- Set maxParallelForks on :core:test to half the available processors, overridable with -PtestForks, and left the small modules single-forked so they do not re-pay the Minecraft bootstrap per fork or steal worker leases from core
- Resolved B.registries() per bound platform instead of latching the first one for the JVM lifetime, keeping the previous unbound behaviour, so a test that binds its own platform no longer reads a foreign platform's block states
- Resolved IrisObject.States per bound platform through accessors instead of static finals fixed at class-init, removing the NoClassDefFoundError and null material-key failures that appeared whenever class partitioning changed
- Bound an explicit platform in EngineMantleCoverageTest and IrisDimensionStackActuatorMetadataTest so they no longer depend on another class leaking a binding first
- Gated BukkitSpiConformanceTest registry probes on a live RegistryAccess and restored the IrisPlatforms binding it leaked, which was silently swallowing the stderr two SettingsHotloadWatchTest assertions read
- Initialised org.bukkit.Registry and Biome inside the RegistryAccess mock scope in ObjectStudioGeometryTest so Mockito no longer instruments Registry mid-clinit
- Replaced the SettingsHotloadWatchTest fixed 1200ms diagnostic window with a 10ms condition poll bounded at 4s
- Replaced the IrisCaveCarver3DNearParityTest wall-clock speedup benchmark with a deterministic density-sample count, cutting 28 carves to 2
- Cut HydrologyPlannerTest cold-owner parity from 12 tile plans to 4 and shared the immutable baseline plan across the early-owner tests
- Built one prototype project or staged history per class in the jigsaw studio and world replacement tests and copied it per test, replacing repeated fsync-bound fixture construction
- Replaced Mockito block-state fixtures with concrete implementations in the object IO, deposit modifier and nearest-block-index tests, removing about 2.5 million interceptions
- Replaced Thread.sleep with bounded condition polling in HydrologyTileCacheTest
- Deleted 19 test classes and 60 further test methods that asserted on production .java source text rather than behaviour, and removed the 19 now-unread iris.*Source system properties that fed them
2026-09-07 19:59:22 -04:00
Brian Neumann-Fopiano d3245aa1c2 IA Fixes
- Thsi fixes an integration between Itemsadder and Oraxen.
- Waiting on oraxen folks
2026-09-07 19:22:59 -04:00
Brian Neumann-Fopiano 894cef73a6 Studio fixes.
- Fixes the Object and Jigsaw studio errors from the coretapper i made.
2026-09-07 15:23:25 -04:00
Brian Neumann-Fopiano 0dcd1900be Improved world generation
- Terrain and cave scans skip repeated work
- River and structure lookups reuse cached results
- Chunk history saves share disk writes
- History lookups continue during disk writes
- Failed saves retain their original errors
- Deposit failures stop incomplete chunks
- Tests check output and save safety
2026-09-07 03:17:09 -04:00
Brian Neumann-Fopiano 151677f92f Improved pregen persistence
- Saved biome records use one durable append with shorter lock ownership
- Terrain receipts buffer field writes before compression
- Native cache retirement follows the current index
- Server shutdown drains native chunk tasks before world close
- Shutdown flushes retain native save ownership
- Regression coverage checks persistence and shutdown races
2026-09-06 18:27:51 -04:00
Brian Neumann-Fopiano 083e689864 Drained pregen before shutdown
- Server shutdown advances native chunk tasks while waiting for pregeneration
- Native world close owns chunk saves during server shutdown
- Pending plugin flushes stop waiting when native shutdown begins
- Late eviction completion cannot schedule another shutdown flush
- Regression tests cover owner waits and shutdown save ownership
2026-09-06 18:20:26 -04:00
Brian Neumann-Fopiano 319f8aed47 Added native chunk draining
- Native chunk queues can advance on their owning server thread
- Folia and foreign threads cannot run global chunk promotion
2026-09-06 18:13:57 -04:00
Brian Neumann-Fopiano 0324b0c467 Added native chunk pumping
- Add target-world native chunk task polling
- Guard server lifecycle ownership and skip Folia polling
- Cover native queue delegation and ownership guards
2026-09-06 18:13:36 -04:00
Brian Neumann-Fopiano 88008a7fd2 Corrected native cache retirement
- Invalidated volume indexes receive runtime retirement callbacks
- Retired builds cannot republish results after cache eviction
2026-09-06 17:55:45 -04:00
Brian Neumann-Fopiano c8fa5d9d8b Fixed native cache retirement
- Transferred retirement listeners when replacing native structure indexes.
- Prevented retired in-flight builds from repopulating origin and query caches.
- Added deterministic invalidation and eviction regression tests.
2026-09-06 17:55:25 -04:00
Brian Neumann-Fopiano bbf14a7967 Reduced biome persistence overhead
- Biome encoding runs outside the region write lock
- Appends use one framed write before durable publication
- Concurrent claims preserve conflict checks and rollback behavior
2026-09-06 17:44:38 -04:00
Brian Neumann-Fopiano b4440463a5 Reduced saved biome writes
- Saved biome encoding runs outside region locks
- Claims recheck stored content before appending
- Record framing uses one write with unchanged bytes
- Tests cover durable publication, rollback and concurrent pruning
2026-09-06 17:43:28 -04:00
Brian Neumann-Fopiano 04103bd15a Fixed pregen shutdown scheduling
- Scheduler admission avoids the plugin manager monitor
- Saving callbacks no longer block the joined pregenerator during disable
2026-09-06 17:41:40 -04:00
Brian Neumann-Fopiano bb967a673e Buffered terrain receipt encoding
- Native terrain receipts batch compressor input
- Receipt format and geometry remain unchanged
2026-09-06 17:41:40 -04:00
Brian Neumann-Fopiano 634e6ffe57 Buffered native terrain receipts
- Terrain fields reach the compressor in buffered batches
- Receipt bytes and decoded natural geometry retain their current format
- Binary contract coverage includes complete and boundary columns
2026-09-06 17:41:38 -04:00
Brian Neumann-Fopiano be621c0074 Fixed pregenerator shutdown lock
- Scheduler admission reads the hosted plugin state without taking the plugin manager monitor
- Shutdown regression joins a worker already dispatching a saving event
- Event tests preserve normal delivery and full failure reporting during scheduler rejection
2026-09-06 17:37:48 -04:00
Brian Neumann-Fopiano 2415825bb7 Unblocked generation stage reads
- Generation stages read immutable metadata without waiting for semantic journal flushes
- Durable claims and activation cutovers retain their synchronization
- Regression tests cover admission contention, publication ordering, and failure cleanup
2026-09-06 16:31:02 -04:00
Brian Neumann-Fopiano 364d127658 Unblocked generation metadata reads
- Opened generation stages independently of semantic journal flushes
- Preserved durable claim publication and cutover ordering
- Added concurrency and failure regression coverage
2026-09-06 16:24:12 -04:00
Brian Neumann-Fopiano 48a81a3d2c Unblocked history stage admission
- Generation stages read coherent metadata from the immutable store manifest
- Stage admission proceeds while unrelated semantic journal flushes hold the history monitor
- Concurrency tests preserve durable publication, cutover ordering, and failed admission cleanup
2026-09-06 16:22:24 -04:00
Brian Neumann-Fopiano 763f9df2c2 Refreshed pregeneration runtime
- Generation router reads avoid the attachment monitor after publication
- Desktop pregeneration uses a proportional map with distinct controls and metrics
- Preview updates remain bounded through large targets and window state changes
- Pause controls stay bound to their owning job
- Startup failures retain a readable error window
- Bundled language catalogs cover the revised window
2026-09-06 14:48:37 -04:00
Brian Neumann-Fopiano 21d6c2c9f9 Corrected pending time estimates
- Preview shows Pending while remaining chunks have no time estimate
2026-09-06 14:43:22 -04:00
Brian Neumann-Fopiano 28c715ff51 Redesigned pregeneration map
- Desktop pregeneration uses a proportional map with labeled controls and metrics
- Pixel updates remain bounded across large targets and minimized windows
- Preview lifecycle keeps pause controls attached to their owning job
- Bundled language catalogs include the revised desktop labels
2026-09-06 14:36:32 -04:00
Brian Neumann-Fopiano e2761af959 Redesigned pregeneration map
- Native window separates map content from progress and resource status
- Pause controls stay bound to their job through shutdown and replacement
- Inclusive map bounds and fixed raster buffers preserve edge updates without idle repaints
- Minimized windows retain bounded status updates while skipping terrain reads
- Startup failures retain a readable error view after background work stops
- All bundled locales include the new controls and metric labels
- Regression tests cover map edges, coalescing, snapshots, and control ownership
2026-09-06 14:36:09 -04:00
Brian Neumann-Fopiano 95323150c0 Reduced coordinate routing contention
- Published router reads bypass the attachment monitor

- Detached routing retains synchronized validation

- Concurrent reads retain coordinate and lifecycle checks
2026-09-06 14:21:54 -04:00
Brian Neumann-Fopiano eb43cc44fa Reduced coordinate routing contention
- Published history router reads bypass the attachment monitor
- Missing router reads retain synchronized attachment and detach checks
- Concurrent tests cover published reads and pending lifecycle changes
2026-09-06 14:21:07 -04:00
Brian Neumann-Fopiano bfaacb4d38 Improved generation runtime stability
- Generation admission reduces contention while preserving cutover priority
- Native POI changes survive generation during server shutdown
- Chunk ownership and retained POI tickets are covered by regression tests
2026-09-06 13:52:07 -04:00
Brian Neumann-Fopiano af42c69824 Preserved native POI ownership
- Native structure placement records pending POI repairs before block changes
- Chunk loads reconcile affected sections on their owning thread
- Matching POI records retain occupied tickets across repair
- Native capability checks preserve supported version behavior
2026-09-06 13:34:50 -04:00
Brian Neumann-Fopiano a28614c04e Fixed native POI ownership
- Native structure POI changes persist dirty chunk sections before deferred updates
- Chunk load reconciles final POI states on the owning thread
- Matching POI records retain claimed tickets
- Native flag detection keeps unsupported server behavior intact
- Regression tests cover persistence and failed repair retries
2026-09-06 13:34:30 -04:00
Brian Neumann-Fopiano 0d2e8a45ff Reduced admission lock contention
- Stage admission avoids FIFO metadata lock handoffs
- Cutover priority and runtime retirement retain their existing checks
2026-09-06 13:16:16 -04:00
Brian Neumann-Fopiano 34c2e4e560 Reduced admission lock contention
- Generation admission uses nonfair metadata lock handoffs
- Waiting cutovers retain priority over later generation stages
- Concurrent stage tests verify cutover drain and runtime retirement
2026-09-06 13:07:52 -04:00
Brian Neumann-Fopiano f3b6a0ec1b Fixed pregen runtime bottlenecks
- Generation routing reduces metadata lock contention while retaining lifecycle ownership
- Native biome wrappers avoid repeated reflection and key allocation
- Studio scoreboards recover from pending saved biome reads with cave overrides intact
- Pregeneration previews retain saved definitions on a bounded renderer worker
- Full build and runtime checks cover scoreboard recovery and Leaf pregeneration
2026-09-06 12:19:31 -04:00
Brian Neumann-Fopiano e5e3ae112d Fixed saved biome scoreboard
- Studio scoreboards recover while saved biome reads are pending
- Combined environment queries retain mantle cave overrides
- World changes refresh rows and genuine failures retain their cause
2026-09-06 12:12:15 -04:00
Brian Neumann-Fopiano 9fdb5615d6 Bounded routing test cleanup
- Shutdown assertions interrupt and drain workers before fallback router close
2026-09-06 12:11:56 -04:00
Brian Neumann-Fopiano 3425c05e8b Fixed saved biome scoreboards
- Keep Studio board refreshes alive while saved biome data loads
- Sample mantle-aware biome environments and clear prior world values
- Report permanent failures once per display context
- Localize loading states and verify recovery and Jigsaw transitions
2026-09-06 12:11:41 -04:00
Brian Neumann-Fopiano fb4585b62d Reduced generation routing contention
- Ready runtime leases avoid repeated metadata lock acquisition
- Concurrent routing retains shutdown and history ownership checks
2026-09-06 12:09:49 -04:00
Brian Neumann-Fopiano e69140e752 Cached native biome handles
- Terrain capture reuses bounded biome wrappers by native identity
- Canonical biome keys use the supported typed accessor
2026-09-06 12:09:49 -04:00
Brian Neumann-Fopiano 6b450fdea4 Fixed saved biome previews
- Pregeneration paints saved biome details on its bounded renderer worker
- Pending queries retain historical definitions through preview consumption
2026-09-06 12:09:49 -04:00
Brian Neumann-Fopiano fca4a58388 Reduced generation routing contention
- Ready generation bindings acquire their lease in one metadata lock pass
- Shared routing metadata uses nonfair lock handoffs under worker contention
- Single-runtime release avoids allocating an empty retirement list
- Concurrent route tests verify shutdown drains and rejects active workers
2026-09-06 12:09:33 -04:00
Brian Neumann-Fopiano cb85a09c0a Cached native biome handles
- Biome wrappers read canonical keys only on cache insertion
- Native handle identity prevents stale biome reuse after registry replacement
- The biome wrapper cache retains at most 4096 entries
- Regression tests cover key reuse, namespaces, replacement handles, and eviction
2026-09-06 12:08:31 -04:00
Brian Neumann-Fopiano df717cf151 Fixed saved biome previews
- Pregeneration previews wait for saved biome reads on the bounded renderer worker
- Preview colors retain historical definitions through rendering
- Existing chunk previews avoid biome reads while the window is hidden
- Query completion handles cache eviction and capture refresh
- Regression tests cover read failures, interruption, and shutdown ownership
2026-09-06 12:05:19 -04:00
Brian Neumann-Fopiano 5570923d30 Fixed saved world startup
- Check spawn chunks asynchronously before hydrology prefetch
- Reject stale runtime callbacks and retain failure stacktraces
- Verify saved-world Folia startup and Paper create unload reload
2026-09-06 05:17:22 -04:00
Brian Neumann-Fopiano d093ff0e41 Fixed Folia spawn initialization
- Check existing spawn chunks asynchronously during world initialization
- Guard delayed hydrology preparation with runtime lifecycle ownership
- Preserve prefetch failure reporting during concurrent shutdown
2026-09-06 05:08:32 -04:00
Brian Neumann-Fopiano 03b0f6e801 Fixed spawn prefetch startup
- Spawn hydrology checks existing chunks asynchronously without generating them
- Prefetch admission rejects closed or replaced generation runtimes
- Studio entry retains tracked hydrology preparation
- Admitted planning failures retain their stacktraces during shutdown
- Regression coverage checks startup admission and delayed completion
2026-09-06 05:08:13 -04:00
Brian Neumann-Fopiano 04471678b2 Refined Iris generation core
- Terrain, hydrology, and feature generation avoid repeated work and temporary allocations
- Deposits preserve configured ordering and independent chance samples
- Object transforms and layer boundaries handle previously failing cases
- Runtime shutdown, storage failures, and world reload retain resource ownership through completion
2026-09-06 04:52:13 -04:00
Brian Neumann-Fopiano f916c81bc0 Restored world reload lifecycle
- Generation admission retires after complete runtime cleanup
- Fresh world loads can activate pending compatible updates
- Failed generator closes remain sealed and retryable
2026-09-06 04:47:26 -04:00
Brian Neumann-Fopiano 2b124a2a25 Fixed world reopen admission
- Generation admission retires after every runtime closes
- Engine setup retains admission across activation replacements
- Failed generator closes stay sealed and retryable
- Regression tests cover reopening and pending promotions
2026-09-06 04:46:38 -04:00
Brian Neumann-Fopiano 62f6a32362 Reviewed object placement changes
- Object transform and smart boring changes remain integrated
2026-09-06 04:19:22 -04:00
Brian Neumann-Fopiano c49a9ddba8 Preserved complex hotload ownership
- Failed replacement cleanup remains owned for shutdown retry
- Published replacements remain current after late activation failure
2026-09-06 04:18:24 -04:00
Brian Neumann-Fopiano d454f25f4d Retained failed hotload complexes
- Retain unpublished complex assemblies when cleanup fails
- Keep published replacement runtimes owned after activation failure
- Cover failed cleanup retries and successful hotload rollback
2026-09-06 04:17:40 -04:00
Brian Neumann-Fopiano 723426177c Separated deposit chance samples
- Clump seeds mix the generator seed and attempt index before sampling
- Deposit plans share immutable seeds across preparation workers
- Probability checks cover generator independence and adjacent clumps across batch boundaries
2026-09-06 04:16:09 -04:00
Brian Neumann-Fopiano 0bfb4d6718 Completed shutdown retry ownership
- Retain generation stages and world services until shutdown completes
- Preserve failed runtime cleanup across resource hash changes
2026-09-06 04:09:34 -04:00
Brian Neumann-Fopiano 046b389925 Completed shutdown retry ownership
- Mark generation modes closed only after every stage finishes cleanup
- Retain manager loop and cleanup executor ownership until their existing shutdown waits succeed
- Propagate manager shutdown timeouts and interruption so resource release remains blocked
- Track unpublished runtimes by identity while resource state changes during cleanup
- Verify failed joins, interrupted waits, stage retries, and changing resource hashes
2026-09-06 04:07:51 -04:00
Brian Neumann-Fopiano fda24db789 Refined featurette edge behavior
- Keep coral tips attached to the fan silhouette
- Preserve large leaf weights and disable zero threshold ores
- Reuse formation palette sources across each object
2026-09-06 03:58:21 -04:00
Brian Neumann-Fopiano 2c66367606 Retained failed generation ownership
- Keep writers until cancelled components actually finish
- Retain incomplete runtime and target ownership for retries
- Gate storage release on completed service drains and saves
2026-09-06 03:58:02 -04:00
Brian Neumann-Fopiano 9a1c3546ba Preserved mantle task ownership
- Keep component cancellation separate from actual completion before releasing writers and deduplication claims
- Reject cancelled queued component claims before they can start writing
- Retain failed runtime assemblies and unpublished runtimes for shutdown retry
- Gate storage and data release on completed runtime cleanup
- Cover cancellation, shared writers, interrupted waits, and failed shutdown retries
2026-09-06 03:57:35 -04:00
Brian Neumann-Fopiano 0efe919ff4 Reused formation palette sources
- Formation resolution shares one base palette source per object
- Strata reuse one seeded source per occupied band
- Mixed palette tests preserve block choices across negative bands and reversed traversal
2026-09-06 03:56:40 -04:00
Brian Neumann-Fopiano ce431b524e Fixed featurette edge cases
- Fan coral tips follow the existing top of each occupied column
- Large secondary leaf weights preserve their authored proportions
- Zero ore thresholds skip noise sampling and block generation
- Regressions cover fan silhouettes, weighted leaf choices, and ore cutoffs
2026-09-06 03:56:26 -04:00
Brian Neumann-Fopiano 2967efbf35 Merged core stability fixes
- Object verification includes stable deposits and featurette fixes
2026-09-06 03:54:14 -04:00
Brian Neumann-Fopiano b3c3a0b183 Bounded feature preparation memory
- Stage at most 32 deposit clumps before ordered placement
- Preserve configured attempt counts and parallel preparation
- Apply consistent sea surface output bounds
2026-09-06 03:48:32 -04:00
Brian Neumann-Fopiano 420148fcc7 Bounded deposit preparation
- Deposit preparation retains at most 32 clumps between ordered placement passes
- Large deposit counts retain parallel preparation and stable per-clump seeds
- Overlap tests cover every attempt across preparation windows
- Deposit tests isolate platform block conversion from cached registry fixtures
2026-09-06 03:46:25 -04:00
Brian Neumann-Fopiano 4c989fe1c0 Merged object stability fixes
- Keep scaling geometry and volume in one locked snapshot
- Skip unsupported block orientations and their tile data
- Replace smart bore executor scaffolding with ordered loops
2026-09-06 03:44:26 -04:00
Brian Neumann-Fopiano 1a2c8a8845 Simplified object smart boring
- Smart boring runs direct axis passes under the existing write lock

- Negative object bounds stop at the last occupied coordinate

- Bore insertion avoids redundant map callbacks

- Regression tests cover ordered fills and repeated calls
2026-09-06 03:43:25 -04:00
Brian Neumann-Fopiano 606aa2e704 Fixed object transform stability
- Placement skips blocks omitted by unsupported rotations

- Rotated object copies omit matching tile data

- Scaling captures geometry and volume under one read lock

- Regression tests cover omitted orientations and concurrent scaling
2026-09-06 03:43:08 -04:00
Brian Neumann-Fopiano 5baea8dd5b Bounded sea surface decoration
- Sea surface decorators respect engine and output ceilings in every placement mode
- Clipped sea stacks retain their top palette
- Tests cover short outputs negative bases and engine ceilings
- Multi-palette tests verify shared random sources match fresh per-block sources
2026-09-06 03:40:27 -04:00
Brian Neumann-Fopiano 75353d43c7 Bounded sea surface decoration
- Sea surface decorators respect engine and output ceilings in every placement mode
- Clipped sea stacks retain their top palette
- Tests cover short outputs negative bases and engine ceilings
- Multi-palette tests verify shared random sources match fresh per-block sources
2026-09-06 03:36:35 -04:00
Brian Neumann-Fopiano 3f9759b05f Merged procedural feature improvements
- Simplify tree fungus and coral generation
- Enforce sea decorator limits and deterministic deposit writes
- Restore branch tip accents outside terminal leaves
2026-09-06 03:34:28 -04:00
Brian Neumann-Fopiano b9b969f1f9 Simplified procedural feature generation
- Fungi build stem roles directly without intermediate maps or coordinate arrays
- Tree palettes reuse one random source per object
- Persistent leaves skip distance traversal when plausibility is disabled
- Tree support scans avoid copied coordinate sets
- Coral arm samples reuse their endpoint array
- Sea decorators share scaled stack limits and respect output bounds
- Unresolved sea floor palette entries preserve existing water
- Tests cover stem geometry persistent leaves and sea stack limits
2026-09-06 03:31:54 -04:00
Brian Neumann-Fopiano 1e3831263c Restored branch tip decorators
- Branch tip accents occupy the first free outward cell beyond their leaf cluster
- Existing wood leaves and accents retain their positions
- Tests cover opposite branch directions and repeated placement
2026-09-06 03:30:29 -04:00
Brian Neumann-Fopiano ebaa616536 Restored branch tip decorators
- Branch tip accents occupy the first free outward cell beyond their leaf cluster
- Existing wood leaves and accents retain their positions
- Tests cover opposite branch directions and repeated placement
2026-09-06 03:29:45 -04:00
Brian Neumann-Fopiano 79690edaaf Stabilized deposit overlap order
- Clumps prepare in parallel and place in dimension region biome order
- Placement evaluates host rules against preceding deposits
- Each clump uses independent deterministic random state
- Configured attempt counts are sampled once per chunk
- Block traversal reuses cursor coordinates
- Tests cover reversed worker completion and generation context cleanup
2026-09-06 03:28:02 -04:00
Brian Neumann-Fopiano 6276bb7327 Merged compact biome selection
- Replace expanded rarity lists with cumulative counts
- Preserve ordinary selection mapping and bound extreme rarity storage
2026-09-06 03:27:31 -04:00
Brian Neumann-Fopiano 587e0fdee0 Merged hydrology planning improvements
- Reuse downstream route distances and primitive footprint indexes
- Bound diagnostic history and reduce containment bookkeeping
2026-09-06 03:27:31 -04:00
Brian Neumann-Fopiano 47f77df0e8 Stabilized deposit overlap order
- Clumps prepare in parallel and place in dimension region biome order
- Placement evaluates host rules against preceding deposits
- Each clump uses independent deterministic random state
- Configured attempt counts are sampled once per chunk
- Block traversal reuses cursor coordinates
- Tests cover reversed worker completion and generation context cleanup
2026-09-06 03:27:23 -04:00
Brian Neumann-Fopiano 534c844a7d Compacted biome selection
- Biome and carving children share compact rarity selection
- Weighted choices preserve alternating insertion order
- Rarity totals use long counts without expanded lists
- Natural child selection preserves its existing noise calls
- Differential tests cover normal and extreme rarity values
2026-09-06 03:24:46 -04:00
Brian Neumann-Fopiano b7e7f64bcf Bounded hydrology diagnostics
- Cold-query log suppression retains a bounded window of tile keys
- Hydrology closure releases cold-query diagnostic history
2026-09-06 03:22:07 -04:00
Brian Neumann-Fopiano 20b9b3dce3 Reduced hydrology planning work
- Downstream route lengths are evaluated once per routing grid
- Footprint construction sorts primitive column keys without a temporary tree
- Footprint lookups use primitive keys and preserve immutable ordered views
- Closed cave components keep one ordered flood-fill membership set
- Surface pools and rivers share their layer construction
- Regression tests cover routing limits, cycles, coordinate bounds, and footprint ordering
2026-09-06 03:21:55 -04:00
Brian Neumann-Fopiano a68cad5893 Merged terrain assembly improvements
- Ceiling layer thickness resolves independently from surface layers
- Locked strata repeat correctly above the internal height reference
- Cave scratch releases generation references after each pass
- Terrain actuators omit redundant setup and checks
2026-09-06 03:16:51 -04:00
Brian Neumann-Fopiano 3ac441484a Merged hydrology shutdown fixes
- Hydrology stops admitting plans during shutdown
- Active planning completes before mantle data is released
2026-09-06 03:16:16 -04:00
Brian Neumann-Fopiano c27a233a50 Retained active generation resources
- Failed planner drains retain mantle and generation data for retry
- Detached runtime cleanup retains its target until runtime teardown succeeds
- Terrain caches clear after hydrology shutdown completes
2026-09-06 03:16:16 -04:00
Brian Neumann-Fopiano 23bad0e1a2 Refined terrain assembly
- Ceiling layers use their own thickness generators
- Pack validation accepts independent ceiling layer counts
- Locked strata repeat across the reference height
- Surface and cave palettes share layer emission
- Cave passes release retained chunk sections
- Nested carving preserves outer blend buffers
- Profile blending reuses bounded numeric buffers
- Terrain actuators remove unused state and duplicate checks
- Regression tests cover layers and carving scratch lifetimes
2026-09-06 03:15:57 -04:00
Brian Neumann-Fopiano dde3c149ad Fixed hydrology shutdown
- Hydrology close drains active tile plans and column composition before returning
- Queued tile plans cancel without waiting for their executor to start them
- Closed caches reject new tile and column demand
- Shutdown tests cover background plans, queued demand, active composition, and recursive close
2026-09-06 03:15:15 -04:00
Brian Neumann-Fopiano 80155cef93 Merged object placement improvements
- Native structure protection uses transformed object bounds
- Object copies share the volume mutation lock
- Upscaling writes expanded voxels without temporary lists
2026-09-06 03:10:04 -04:00
Brian Neumann-Fopiano 057d51f211 Completed offline decorator hooks
- Offline generation resolves decorator support without Bukkit classes
- Probe coverage checks solid, fluid and air spike support
2026-09-06 03:10:04 -04:00
Brian Neumann-Fopiano 23316d2d5e Reduced object scaling allocations
- Enlarged objects write expanded cells through one reusable coordinate
- Scaling removes temporary voxel lists and unused bound reads
- Expanded tiles retain independent copies and existing write order
2026-09-06 03:08:00 -04:00
Brian Neumann-Fopiano d55c77572b Fixed object placement bounds
- Native structure vetoes use the full transformed object bounds
- Object copies hold the volume read lock during mutation-sensitive reads
- Object copies retain independent tiles without redundant coordinate clones
- Regression tests cover oblique rotations and off-center object origins
- Concurrent copy tests verify volume lock ownership
2026-09-06 03:08:00 -04:00
Brian Neumann-Fopiano 942545dcb9 Fixed mantle failure barriers
- Worker barriers propagate failed shared component tasks
- Cancelled shared tasks stop generation before later passes
- Local worktree directories remain excluded from source control
2026-09-06 03:06:52 -04:00
Brian Neumann-Fopiano 17c927acdd t 2026-09-06 02:33:09 -04:00
Brian Neumann-Fopiano 13263f1a03 Removed Iris CI
- Removed the GitHub Actions workflow for pushes and pull requests
2026-09-05 21:48:50 -04:00
Brian Neumann-Fopiano 3a0e4557b7 Fixed cave biome identity
- Missing cave biome identities use the column surface biome
- Capture tests cover omitted caves, configured caves, and invalid surface keys
- Runtime scope tests retain keyed cave biome fixtures
2026-09-05 21:18:05 -04:00
Brian Neumann-Fopiano f8cbbde683 Merged Iris updates
- Historical biome retention is integrated

- Replacement staging progress and admission fixes are integrated
2026-09-05 20:37:46 -04:00
Brian Neumann-Fopiano 3f1448dc33 Merged replacement staging fixes
- Replacement progress and admission fixes are integrated with retained biome behavior
2026-09-05 20:31:17 -04:00
Brian Neumann-Fopiano 80843114fc Merged biome retention
- Saved biome behavior is integrated with current Iris changes
2026-09-05 20:30:52 -04:00
Brian Neumann-Fopiano b17fe811f3 Preserved historical biome behavior
- Generated chunks retain surface and vertical biome assignments
- Floating biome assignments persist through terrain transitions
- Archived pack definitions remain available for saved biome rules
- Spawns and effects use each saved biome environment
- Saved biome lookups load asynchronously with bounded caches
- Missing biome identities recover only from exact recorded evidence
- Effects and entity placement follow existing world surfaces
- New pack fingerprints ignore Finder metadata
- Tests cover biome storage, recovery, consumers, and scoped definitions
2026-09-05 20:30:50 -04:00
Brian Neumann-Fopiano 27d511d99a Fixed replacement staging stalls
- Concurrent replacement requests report busy before waiting for the manager lock
- Replacement staging reports localized phases and elapsed time
- Player progress messages use entity scheduling
- Pack copies flush completed files after traversal
- Replacement admission and progress tests cover scheduling and cleanup
- Language editor tests use a local catalog response
2026-09-05 20:30:30 -04:00
Brian Neumann-Fopiano c53e9f8261 Wand fix for WE 2026-09-05 20:24:01 -04:00
Brian Neumann-Fopiano 064ec0cae9 Merged noise refinement
- Existing noise corrections and fifteen new styles are integrated
2026-09-05 19:38:41 -04:00
Brian Neumann-Fopiano 14cfe3ed68 Added diverse noise styles
- Dune and Strata add dunes and layered bands
- Wood and Marble add warped grain and mineral veins
- Gabor and Scales add oriented waves and overlapping scallops
- Chladni and Kaleidoscope add nodal lines and mirrored motifs
- Menger Sponge and Circuit add recursive voids and connected traces
- New styles use the shared scale and full seeds
- Tests cover tile seams, volume variation, bounds, and octaves
2026-09-05 19:33:53 -04:00
Brian Neumann-Fopiano 645edcff5c Added patterned noise
- Gyroid and Quasicrystal provide labyrinths and wave interference
- Truchet tiles join quarter-circle ribbons
- Crater and Vortex provide bowls and spiral fields
- Pattern samplers share bounded octave accumulation
- Tests cover seeds, geometry, volume sampling, and world edges
2026-09-05 19:33:14 -04:00
Brian Neumann-Fopiano 50894b90ce Refined existing noise
- Hexagon styles produce coherent solid cells with contained subdivisions
- Sierpinski triangles use equilateral recursive geometry
- Noise styles share a 64-block base scale
- Zoom scales the complete pattern with its distortion
- Octaves preserve the base seed and feature scale
- Coordinate dispatch uses the native dimensional sampler
- Classic kernels retain seed precision and bounded output
- Interpolation preserves fractional coordinates and symmetric curves
- Noise caches respect sampling bounds and generation identity
- Clover sampling reuses vertices and cached trigonometry
- Noise and terrain tests cover corrected sampling behavior
2026-09-05 19:32:13 -04:00
Brian Neumann-Fopiano f2887632fe Dicing things up a little
- Trying to slim this beauty down to a size 6.
- Shes a size 7.1
2026-09-05 16:45:31 -04:00
Brian Neumann-Fopiano d10d70a894 Merged terrain generation updates
- Existing worlds retain generated terrain across generator updates
- New terrain blends at generation boundaries
- Cold generation and Studio startup improvements are integrated
- Terrain sampling validates active generator bounds and noise
2026-09-05 12:32:03 -04:00
Brian Neumann-Fopiano 5a5e8a8dc8 Validated terrain samples
- Terrain sampling rejects non-finite active bounds and noise before caching heights
- Validation tests cover invalid samples and unchanged finite arithmetic
- Bukkit packaging uses the existing 8 MB artifact limit
2026-09-05 12:31:36 -04:00
Brian Neumann-Fopiano 965606bbcc Merged generation improvements
- Cold startup and hydrology changes integrate with terrain history
- Flat authoring worlds skip native features under the history write guards
- Native decoration checks retain recorded structure and terrain ownership
- Automatic build revisions replace manual source seals
2026-09-05 12:05:14 -04:00
Brian Neumann-Fopiano b1e1fbc15e Preserved terrain across updates
- Saved chunks retain their natural geometry and generation ownership
- Future chunks blend against frozen terrain boundaries
- Build revisions capture generation source and dependencies automatically
- Native terrain queries use contained transition geometry and final height caches
- Historical searches retain recorded pack identities and placement facts
- World creation and Studio updates preserve native lifecycle ordering
- Hydrology containment and scoped content workers retain generation context
- Pregeneration validates saved completion before trusting cached progress
2026-09-05 11:57:01 -04:00
Brian Neumann-Fopiano 73d640943c Improved cold generation
- Flat authoring studios skip terrain and native structure preparation
- Normal Studio entry prepares the exact hydrology column asynchronously
- Hydrology prefetch admits the complete frontier before stale queued work
- Concurrent hydrology loads avoid cache monitor contention
- Cave density tasks drain failures before releasing chunk state
- Terrain and interpolation queries reuse exact bounded samples
- Pregeneration counts requested chunks directly from its bounds
2026-09-05 11:57:01 -04:00
Brian Neumann-Fopiano 20ad49b9d2 Merged generation history
- Integrated immutable generation epochs after dimension stacking
- Preserved historical terrain, biome, structure, and locate behavior
- Validated combined Bukkit and modded generation contracts
2026-09-04 15:07:18 -04:00
Brian Neumann-Fopiano 56b5f4dd0b Merged generation history
- Immutable generation epochs now coexist with dimension stacks
- Historical terrain, biomes, structures, and locate behavior remain stable across updates
- Versioned generation kernels preserve the combined runtime contract
2026-09-04 15:06:55 -04:00
Brian Neumann-Fopiano 8247f167cf Merged dimension stacking
- Upright stacked dimension generation is now part of master
- Superflat generation coverage is now part of the Iris test suite
2026-09-04 14:23:38 -04:00
Brian Neumann-Fopiano 6549b40797 Added dimension stacking
- Dimension files define upright top-to-bottom terrain layers
- Spacer values set exact air gaps between layers
- Optional noise styles shape blended layer boundaries
- Runtime queries resolve stacked terrain and biome ownership
- Pack validation rejects invalid stack definitions
- Project copy and hotload paths include stack dependencies
- Superflat fixtures cover gaps, blending, clipping, and fluids
- Platform tests cover stacked world generation contracts
2026-09-04 14:23:15 -04:00
Brian Neumann-Fopiano 30113c4133 Added generation history
- World-local pack epochs preserve generated chunks across restart-only updates
- Kernel catalogs and source seals retain executable generation behavior across jar changes
- Crash-safe ownership, boundary, registry, semantic, and mantle records survive activation changes
- New terrain blends lower surfaces and inverted ceilings against frozen historical boundaries
- Cave, object, structure, biome, and hydrology work respects activation ownership
- Find commands merge recorded facts with eligible active predictions for biomes, regions, structures, objects, and rivers
- Bukkit, Fabric, Forge, and NeoForge route generation through retained runtime scopes
- Shutdown ordering preserves class-loader resources until world and service cleanup finishes
- Tests cover activation chains, failure recovery, retained registries, bounded lookup, and transition geometry
2026-09-04 13:57:39 -04:00
Brian Neumann-Fopiano c1bcdd0a9d Reduced jar container overhead
- Seekable ZIP output removes redundant data descriptors without changing entry contents
- Compaction validates entry size and CRC before replacing the artifact
- Tests cover metadata preservation, deterministic output, corrupt payloads, and duplicate entries
2026-09-04 13:36:14 -04:00
Brian Neumann-Fopiano 366033a535 CORRECTIONS
- this is the corrections from the afoemrnioned commits
2026-09-04 00:02:43 -04:00
Brian Neumann-Fopiano a771fa6856 BuildFix 2026-09-03 16:38:23 -04:00
Brian Neumann-Fopiano a368d42558 Terrain is important
- Fixed Ores generating all over the surface.
- fixed UI bug
2026-09-03 15:20:51 -04:00
Brian Neumann-Fopiano 0510c108be Disregard. 2026-09-03 14:01:45 -04:00
Brian Neumann-Fopiano e4ca7d4e70 Merge feature/version-compat: gate pack content on the running Minecraft version
- Every registrant is evaluated on load (IrisData.preprocessObject ->
  IrisRegistrant.evaluateCompat) by a cached per-class reflective walker over the
  annotated key fields; the verdict is stored on the registrant and pool sites filter
  with isCompatExcluded(). Block keys resolve through the live registry, the legacy
  rename table, IrisDimension.blockFallbacks and the entry backup, in that order.
- Unit of exclusion is the compat unit that composes the missing content (registrant
  or object placement). A referencing unit drops the reference; an emptied required
  pool excludes the container (CompatPools cascade through regions, biomes, dimension,
  spawners, loot tables, jigsaw pools and structures). A cascade that reaches the
  dimension is a blocking validation error and world creation is refused.
- Object placements are evaluated per object from the .iob palette header: an object
  is excluded from a placement's pool unless a chance-1 type-replace (edit rule
  matching the key) or a fallback covers the key. edit find lists and marker mark
  lists are match-only. Missing palette keys stay MissingBlockState placeholders
  (air everywhere, real air nativeHandle) so a chance-1 edit rule can still rewrite
  them at placement time.
- Approved decisions: exclude the composing unit; console-only reporting; no version
  fields on packs and no cross-version pre-check (authors build on their version,
  older servers gate at load).
- IrisDimension.blockFallbacks (base key -> full block state) applies to every
  palette, decorator, deposit, replace rule and object in the pack.
- IrisBlockData.backup is revived on every platform (it was dead on Bukkit because
  B.getState returned air).
- The legacy rename table now lives in LegacyBlockRenames and is applied identically
  on Bukkit and the mod loaders.

Operator surface
- Boot listing per pack after the validation lines (IrisWorldGeneratorResolver on
  Bukkit, ModdedStartup on modded), grouped by key, capped at three subjects per key.
- Validation summary line on /iris pack validate and /iris pack status, findings
  persisted in PackValidationResult through PackValidationCache.
- One summary line per world engine when its pack has findings.
- /iris pack compat [pack] (alias cp) on Bukkit and modded, uncapped, from the
  persisted validation so no pack is reloaded; catalog keys in all 17 locales.

Verification on the branch
- Full build green in the .compatwt worktree; modded adapters compile.
- Golden hash unchanged on Paper 26.2, seed 1337, radius 8.
- Live boots on Paper 26.1.2 and 26.2: the stock Overworld lists 6 unavailable keys
  on 26.1.2 and none on 26.2; the world creates and generates without the excluded
  content on 26.1.2.

Integration on top of master (df34f328e..fc3a26dd9)
- Conflicts: PackValidator keeps both import sets (JSONObject for static object
  validation plus the compat and loader types); RiverPolicyResolver.State.apply takes
  the confinement scope and the pack data (confined rivers and gate-filtered river
  biome selections); IrisRiverPolicy keeps shoreBiomeWidth and confined next to
  compatBiomes.
- Static objects honour the gate. The per-object palette evaluation moved out of
  IrisObjectPlacement into the shared IrisObjectCompat helper; IrisStaticObject
  .evaluateCompat mirrors a one-object placement, records findings with subject type
  "static object" and the object key (DROPPED for uncovered palette keys, EXCLUDED
  for a missing edit replace palette), IrisStaticObjectLayer.compile skips gated
  entries instead of failing, and PackValidator.gateContent asks every dimension for
  its static objects (IrisDimension.evaluateStaticObjectsCompat) so the boot listing
  is complete. CompatWalker treats IrisStaticObject like IrisObjectPlacement: never
  descended, so a static object's edit palette excludes only that entry, never the
  dimension. Covered by IrisStaticObjectCompatTest.
- The modded compat and cp subcommands run inside ModdedCommandTree.localized like
  the rest of the pack tree and are documented in the modded help; the parity test
  asserts both.
- Locale keys stay in core/src/main/resources/languages: master's on-demand download
  reads those files from the repository and the build only keeps them out of the jar,
  so the compat keys sit next to the debugdump keys in the same form.
- Placeholder audit of master's new code: the only native casts added are the
  isFaceSturdy fallbacks in IrisSpeleothems and ModdedDecoratorHooks, both behind
  B.isAir guards, and MissingBlockState.nativeHandle is the real air anyway;
  IrisObjectTransforms only asks B.isAir; decorators resolve palettes through
  IrisBlockData.getBlockData, which never yields a placeholder. No new guards needed.
- core/purity-allowlist.txt gains IrisSpeleothems: 5053a644c split the Bukkit
  isFaceSturdy fallback out of the already allowlisted DecoratorCore without moving
  the allowlist entry, so master's own build failed the Bukkit purity ratchet.
- CompatTestPlatform states now answer isAir, isSolid, isFluid and materialKey like a
  real adapter: B caches the first registries it sees per JVM, so an interned mock
  air that never said isAir leaked into the core.compat tests once the static layer
  ran under that platform.

Gate commands on the merged tree
- ./gradlew build (default VolmLib composite, ../VolmLib at 7b8029e): BUILD SUCCESSFUL;
  core 3499 (8 skipped) tests, bukkit plugin 343 tests, nms v26_2_R1 255 (1 skipped) tests,
  probe 78 tests, buildSrc 10 tests, 0 failures.
- cd adapters/fabric && ../../gradlew compileJava: BUILD SUCCESSFUL (:compileJava executed)
- cd adapters/forge && ../../gradlew compileJava: BUILD SUCCESSFUL (:compileJava executed)
- cd adapters/neoforge && ../../gradlew compileJava: BUILD SUCCESSFUL (:compileJava executed)
- cd adapters/forge && ../../gradlew test --tests IrisModdedCommandParityTest --tests
  ModdedLanguageCommandsTest: BUILD SUCCESSFUL, 11 tests, 0 failures.
2026-09-03 10:48:37 -04:00
Brian Neumann-Fopiano fc3a26dd9e Ignore the compat lane worktree
The .compatwt/ directory holds a generated worktree for the
version-content compat lane and is never source.
2026-09-03 05:03:33 -04:00
Brian Neumann-Fopiano 657aaeaccf Add /iris language and /iris debugdump on both platforms
/iris language opens a locale menu and sets the language for the caller
or, for a gamemaster, the server default; /iris debugdump writes a
diagnostic report and optionally uploads it. Neither needs iris.all, so
a plain player can reach them. Every modded command now executes inside
a language audience scope, and the Bukkit dispatcher opens one around
execution, tab completion and help. Permissions, message keys and the
shipped locales follow.
2026-09-03 05:03:28 -04:00
Brian Neumann-Fopiano b25937f986 Download translations on demand instead of bundling them
Locales are fetched from the Iris repository at runtime into
languages/downloaded and cached against the revision the jar was built
from; the artifact ships only a manifest of the available locales.
IrisLanguage gains a player language service, per-audience snapshots and
an in-game message editor that writes locale overrides. The Bukkit
artifact verifier now fails when any locale file is embedded, and the
modded shadow jars drop the Bukkit-only switcher and editor classes.
2026-09-03 05:03:21 -04:00
Brian Neumann-Fopiano 5053a644c7 Grow speleothems from decorators and keep them supported
Dripstone and sulfur spikes are placed as oriented, thickness-graded
columns by the surface and ceiling decorators through a shared
IrisSpeleothems helper, replacing the ad hoc dripstone fixups, and the
stacking path is unified for both directions. The perfection modifier
renormalizes a spike whose support a pool replaced, dropping the
unsupported run and re-tipping what remains. Surface sturdiness is asked
per face now, so a downward spike tests the ceiling it hangs from.
2026-09-03 05:03:14 -04:00
Brian Neumann-Fopiano fd66b41bad Answer server-thread terrain queries from natural terrain
A height or biome query from the tick thread no longer blocks on a cold
hydrology plan: it answers from the natural streams and asks the
planning pool for the tiles meanwhile. A new world prefetches the tiles
around spawn when the generator is injected, and the hydrology pool gets
full parallelism instead of a third. Also stops the shared corner-bounds
cache from pinning a non-finite sample and fences its two stamp reads.
2026-09-03 05:03:07 -04:00
Brian Neumann-Fopiano a4fe527d65 Polish surface rivers: estuaries, ponds, erosion, sea caves
Surface rivers gain a drowned inlet at the mouth, a spring pond and an
inland terminal pond, a configurable erosion profile (thalweg fraction,
blend curve, bed noise, smoothing) and a per-area shore biome width;
banks lose freeboard and inset for a single sink. Underground courses
accept tributaries, coastal grottos can be admitted as standalone sea
caves, and a region or biome can confine the rivers born in it. Tile
planning forks its ring work through HydrologyForkJoin. Pack validation,
the schema, the probes and the tests follow.
2026-09-03 05:03:00 -04:00
Brian Neumann-Fopiano e92c0efe09 Keep native structures out of static object cells
Vanilla structure and feature placement now runs through the bounded
worldgen access with a protected-position predicate, so a jigsaw piece
or an imported feature can read a static object's blocks but never
overwrite them, and never schedules a tick or block entity there.
Dimensions without static objects keep the plain level as before.
2026-09-03 05:02:53 -04:00
Brian Neumann-Fopiano 0a791bb8bf Place static objects at absolute world coordinates
A dimension can now list staticObjects: an object key, a world position, a
fixed rotation and scale, block edits and an optional bore or smart bore.
The layer compiles once per dimension and stamps its blocks into every
newly generated chunk it covers, later entries winning overlaps. Pack
validation, the schema, the export closure and the object transforms
(origin-anchored scaling, tile rotation) cover the new fields.
2026-09-03 05:02:45 -04:00
Brian Neumann-Fopiano 0b543bf7ee Compat report, validation and commands
- Force-load every registrant and placement during pack validation and persist the findings with the Minecraft version in the validation cache
- Turn an excluded dimension into a blocking validation error and suppress content-key warnings the gate already reported
- Print the capped per-key listing after the validation lines on Bukkit and modded, ordered excluded, dropped, substituted
- Log one summary line per world engine from the published validation result
- Add /iris pack compat on Bukkit and modded with help and locale keys for every shipped language
2026-09-03 04:54:38 -04:00
Brian Neumann-Fopiano 0d464aabf3 Gate pools, placements and structures
- Filter excluded biomes, regions, spawners, loot tables, jigsaw pieces and structures out of every pool through CompatPools and cascade empty pools to the container
- Evaluate each object placement per object from the .iob palette header, honouring chance-1 edit rules and reporting every missing palette key
- Treat edit find lists and marker mark lists as match-only
- Exclude jigsaw pieces on missing palette keys, drop them from pools and exclude structures whose start pool is gone
- Drop custom biome spawns whose entity is missing before the datapack JSON is written
- Skip excluded placements in the object, floating object, tree and locator paths while keeping the per-chunk RNG draws unchanged
- Keep the engine focus biome and region null when the gate excluded them
2026-09-03 04:54:35 -04:00
Brian Neumann-Fopiano 516ef71c9c Version content gate core
- Add the ContentGate, CompatWalker, PackCompatReport and finding types under core.compat
- Resolve block keys through the live registry, the legacy rename table, dimension blockFallbacks and the entry backup on every platform
- Move the legacy rename table into LegacyBlockRenames so Bukkit and the mod loaders apply it identically
- Evaluate every registrant on load from IrisData.preprocessObject and store the verdict on IrisRegistrant
- Add IrisDimension.blockFallbacks as a plain object property
- Keep missing palette keys as MissingBlockState placeholders so a chance-1 edit rule can still rewrite them
- Guard the modded rotator, merger and face fixer against non-modded states
- Source the legacy potion effect aliases from the gate so the Bukkit-typed table stays unloaded on modded
2026-09-03 04:54:22 -04:00
Brian Neumann-Fopiano df34f328e7 Pregeneration throughput
A pregeneration now generates in multicore mode: the matter stage runs
on the calling thread so its mantle window fans out across the burst
pool while biome and terrain run alongside, components are claimed
through the in-flight map so overlapping windows join instead of
blocking, and each region's chunks are ordered in stride-4 lattices so
consecutive chunks never share a window. Hydrology tiles for the whole
area are planned ahead nearest the centre first on a dedicated planning
pool, and the ring around every needed tile stays planned as the front
moves. Height bounds at grid corners are shared between threads and
from planning into generation, the sole engine of a pack is answered
from a snapshot instead of the registry lock, composed chunk keys are
mixed, cached tiles are answered in place, the burst pool grows to two
workers per core for the run, and large chunk-worker pools admit up to
256 requests.
2026-09-02 13:56:58 -04:00
Brian Neumann-Fopiano 88bd8feeeb A hydrology tile that fails to plan generates without rivers
Planning failures used to escape into the chunk system and crash the
server. The tile cache now catches them, logs the failure in full and
publishes an empty tile so terrain keeps generating. A natural height
that is not finite is no longer memoized, and its error names the
region, biome, fluid height, overlay, every interpolator's bounds and
generator heights, and the cached and fresh values for the column.
2026-09-02 11:46:30 -04:00
Brian Neumann-Fopiano e63fd9c448 Describe the river placement, routing and grotto pool enums for the schema 2026-09-02 11:46:29 -04:00
Brian Neumann-Fopiano 1c577fc580 Fit the Bukkit artifact to VolmLib master
VolmLib master added remote language catalogs and TOML language tooling.
The TOML language editor and parser depend on the excluded config
package and nothing in Iris uses them, so they are excluded too, and the
artifact size budget rises to 7.5 MB for the shipped localization
classes.
2026-09-02 03:08:27 -04:00
Brian Neumann-Fopiano 665b201e41 Merge terrain-first hydrology into master
Surface rivers v2: valley-first eroded channels, spring pools, standing
pools, tributaries, bed padding, ring-ordered feature search, schema v2
hydrology configuration and the transect probe.
2026-09-02 03:00:10 -04:00
Brian Neumann-Fopiano 333b766d2d Datapack imports from Modrinth, HTTP(S) and local file ZIPs
Work in progress parked from the working tree so the hydrology merge
could land; amend or squash as needed.
2026-09-02 03:00:10 -04:00
Brian Neumann-Fopiano f6a7ad7472 Merge origin/master into terrain-first hydrology
Brings master's Sentry removal, bound platform classification, Vision
entity toggle, release version and generation context binding onto the
rivers rework while keeping the hydrology system master had removed.
Hydrology files master deleted are restored from the branch, master's
non-hydrology edits are kept, and the modded adapter builds drop the
Sentry dependency with master.

Also removes the ridge_tunnel find selector: no course publishes ridge
bores any more, so it could never return a result.
2026-09-02 02:52:08 -04:00
Brian Neumann-Fopiano d23d6d5c9b Tributaries join the main stem at a confluence
- routing.tributaries lets an outlet keep extra surface courses; each is cut where its centerline first meets the stem and joined with a short connector
- A tributary must keep half the minimum course length and arrive at or above the stem's water
- Surface shape rejections carry the failing rule as their detail
2026-09-02 00:54:22 -04:00
Brian Neumann-Fopiano 185e2fa4ae Planner probe test excludes standing pools from required coverage 2026-09-02 00:40:13 -04:00
Brian Neumann-Fopiano 3ec5fab70b Planner probe does not expect standing pools without pool settings 2026-09-02 00:32:25 -04:00
Brian Neumann-Fopiano 479b94d6a2 Standing surface pools
- hydrology.surfacePools defines bowls filled with their own fluid; riverPolicy.surfacePools opts areas in
- Pools are independent SURFACE_POOL courses with one STANDING_POOL segment, shaped by the erosion field, kept clear of rivers and off slopes
- Locators accept the pool selector and pool ids; validator, renderer, probes and docs follow
2026-09-02 00:29:32 -04:00
Brian Neumann-Fopiano 13bce7aa42 Rejection details on surface course diagnostics
Valley solver rejections carry the failing measure (required cut, exposed stations, missing head) through the planner diagnostics; the transect probe prints them.
2026-09-02 00:05:10 -04:00
Brian Neumann-Fopiano 8848167c22 Surface drainage never climbs beyond the permitted cut
- Lattice edges that rise more than the cut the valley solver accepts are not surface drainage
- Ocean reachability uses the same rule, so ground behind a ridge gets inland grotto outlets instead of an impossible mouth
- Transect probe renders a tile diagnostics map with the drainage graph and rejected candidates
2026-09-01 23:46:11 -04:00
Brian Neumann-Fopiano f4a76dfa85 Cost cross-slope drop in route refinement
Refined routes now pay for ground that falls away beside them, keeping channels on valley floors instead of hillside traverses the incision cap rejects.
2026-09-01 23:38:32 -04:00
Brian Neumann-Fopiano 4b497a7d20 Prefer longer routes when ranking river sources
routing.lengthPreference adds route length to the source score so outlets take their longest legal course.
2026-09-01 23:33:15 -04:00
Brian Neumann-Fopiano 6945cbfd9b Spring pool tests and single source marker
- Only the centerline cell of the first station carries the source feature
- Headwater and styled geometry tests expect the spring pool
- Transect table starts with the first 24 stations
2026-09-01 23:21:44 -04:00
Brian Neumann-Fopiano d1c73025a0 Spring pools at headwaters and ring-ordered feature search
- channel.springWidthRatio / springLength open every surface river in a pool that shrinks where the ground falls away
- Banks settings keep structural invariants only; authoring bounds stay in the validator
- Feature searches walk tiles outward and stop early; find and goto report progress per ring
2026-09-01 23:13:47 -04:00
Brian Neumann-Fopiano 896e58e212 Delete the dead v1 hydrology settings surface
- Surface inset, terrain blend and ridge tunnel settings gone; Hydraulics keeps only waterfallMinimumDrop
- Branching folded into Routing; refinementSpacing derived from the sample lattice
- Contained surface routing branch, surface incision helpers and 13 unreachable compiler members deleted
2026-09-01 23:08:47 -04:00
Brian Neumann-Fopiano 639786537b River bed padding and feature search rings
- surface.bed: falling blocks under and beside river water are replaced by a padding palette unless allowed
- Ring order helper for bounded feature searches
2026-09-01 23:08:47 -04:00
Brian Neumann-Fopiano 1ad3a25cb6 Object scatter test follows the channel-or-shore veto 2026-09-01 21:57:51 -04:00
Brian Neumann-Fopiano 507e241b2c Legacy hydrology tests on the v2 contract; water connects across steps
- Geometry pins became relational properties; tests for deleted mechanisms removed
- Every owned channel water column is published to the mantle so steps get fluid updates
- Upper beds at head steps reach the lower head so the water stays one body
- Ocean aprons are measured from the first ocean station and bounded laterally
- Dry cave headroom uses the flooded cave biome
2026-09-01 21:54:34 -04:00
Brian Neumann-Fopiano 3d96a8616a Hydrology probes on the rivers v2 contract
- Ocean integrity fires on land columns near a mouth; shallow incision and bank continuity are gates
- Probe-local centerline and turn helpers replaced by production geometry
- v1 checks for ridge bores, endpoint ramps, terminal caps, transition recovery and pool lengths removed
2026-09-01 21:39:46 -04:00
Brian Neumann-Fopiano 0c317929f8 Transect probe lists rejection reasons per tile 2026-09-01 21:32:48 -04:00
Brian Neumann-Fopiano 4c4153debd Valley shaping fixes from the first transects
- Head ring now covers every cell that can sit beside water
- Blend widths are smoothed along the course; the bed is a broad bowl
- Diagonal shore cells keep the lip; cross-section rows belong to their own station
- Transect probe prints coordinates and numeric section tables
2026-09-01 21:29:08 -04:00
Brian Neumann-Fopiano 124bcd9372 Align schema bounds with the hydrology validator
Studio schema annotations now match the validator ranges; mouthLevelingDistance must fit inside maximumRouteLength.
2026-09-01 21:15:36 -04:00
Brian Neumann-Fopiano 00f5a16c20 Validator for rivers schema v2 and dead planner code removal
- PackHydrologyValidator rewritten for surface.channel/banks/flow/mouths, routing weights and bankBiomes
- 76 unreferenced private members deleted from HydrologyPlanner and HydrologyFootprintCompiler
2026-09-01 19:30:29 -04:00
Brian Neumann-Fopiano b57a2e2fc8 Remove the 2D cache bypass
Hydrology sampling now goes through the engine 2D caches; the thread-local bypass and its test are gone.
2026-09-01 19:22:42 -04:00
Brian Neumann-Fopiano 5b1b636539 Point basis samples carry terrain slope
Routing, validation and the valley solver sampled basis columns with slope 0; the forward slope now rides on every point sample.
2026-09-01 19:21:25 -04:00
Brian Neumann-Fopiano 2585b2105a River transect probe
- riverTransectProbe renders each surface course as a plan and five cross-sections
- Summary gates ocean writes and spilling channel cells
- Grading columns now report the bank biome
2026-09-01 19:18:59 -04:00
Brian Neumann-Fopiano 676372efab River world hooks
- Skip nib, pothole, wall and slab post passes on river footprints and their collar
- Keep automatic object scatter off the channel and shore band
- Expose deeper biome layers on eroded banks and use the bank biome on the blend band
- Give dry cave headroom the flooded cave biome
2026-09-01 19:08:20 -04:00
Brian Neumann-Fopiano 8a256e3226 Surface course integration
- Build surface courses through the valley solver and segment labeler
- Compile surface footprints through the erosion field and index them for cave containment
- Drop the contained-surface fallback and the post-hoc surface rejection passes
- Choose the river profile at the source
2026-09-01 19:05:43 -04:00
Brian Neumann-Fopiano 2c6792977f Surface valley model
- Add the surface package with centerline densification, channel profile, valley head solver and erosion field
- Derive the head from the bank ring outside the channel so every wet cell is contained by construction
- Stamp one continuous cross-section with a cut-scaled blend and a freeboard lip, filtered at the ocean
2026-09-01 18:56:14 -04:00
Brian Neumann-Fopiano 915cc1a2f0 Surface settings v2
- Replace hydraulics, ridge tunnel, branching and inset ranges with channel, banks, flow and mouth configs
- Add routing cost weights, underground mouth leveling, bank biomes and bank multiplier
- Map the new schema into planner settings and derive refinement spacing
2026-09-01 18:46:12 -04:00
Brian Neumann-Fopiano 48afa0d93f Terrain-first hydrology
- Replace the river package with the hydrology planner, footprint compiler, cave containment and runtime
- Add the hydrology pack schema, validator, probes and tests
- Wire hydrology into IrisComplex, actuators, mantle, Vision and commands
2026-09-01 18:28:00 -04:00
Brian Neumann-Fopiano 15bc52e9b5 V+ 2026-08-28 17:06:58 -04:00
Brian Neumann-Fopiano 9103796845 Toggle Vision entities
- Hide living-entity markers by default while retaining player markers.
- Add an Entities control beside Grid and stop polling hidden entity snapshots.
- Localize the new control and status messages across all bundled languages.
- God i gate these kinds of commit messages
2026-08-28 16:41:06 -04:00
Brian Neumann-Fopiano 27799039d1 Bind generation context
- Resolve first-generation engine binding from authoritative published level context.
- Validate generator identity and recover canonical Overworld binding from stale snapshots.
- Add shared loader regression tests and their Mockito test dependency.
2026-08-28 16:30:11 -04:00
Brian Neumann-Fopiano fd8c55076e Bridge host levels
- Publish and remove dynamic levels through host lifecycle methods when available.
- Attach hybrid host level data before engine binding and publication.
- Preserve native loader behavior when host extensions are absent.
2026-08-28 16:30:05 -04:00
Brian Neumann-Fopiano 95d8523d1d Route hybrid schedulers
- Select Bukkit scheduling only when the bound platform identifies as Bukkit.
- Route hybrid Youer hosts through the modded scheduler despite Bukkit classes on the classpath.
- Add regression coverage for Bukkit and NeoForge platform identities.
2026-08-28 16:29:58 -04:00
Brian Neumann-Fopiano ecec18509c Honor bound platform
- Route shared runtime hooks through the active Iris platform instead of classpath presence.
- Keep Bukkit-specific mantle and Matter behavior behind explicit platform capabilities.
- Cover hybrid classpaths and bound hook precedence with regression tests.
2026-08-28 16:29:53 -04:00
Brian Neumann-Fopiano 1ca943ab51 Remove Sentry
- Remove automatic error reporting and server-identifying attachments.
- Remove Sentry dependencies, release tooling, settings, and test commands.
- Keep failures operator-visible through local stack traces and logging.
2026-08-28 16:29:33 -04:00
Brian Neumann-Fopiano 1a561da2cd update gradl 2026-08-27 19:24:33 -04:00
Brian Neumann-Fopiano 2b92fbf825 iris release commits 2026-08-27 02:47:05 -04:00
Brian Neumann-Fopiano f398c55091 Remove Hydrology 2026-08-27 00:45:15 -04:00
Brian Neumann-Fopiano bdaa84975a f 2026-08-25 15:49:54 -04:00
Brian Neumann-Fopiano 07cc76614f Way too much 2026-08-25 12:52:12 -04:00
Brian Neumann-Fopiano 2797a5bf22 dwa 2026-08-24 16:50:57 -04:00
Brian Neumann-Fopiano 37b01b73c4 Imagemap sanitization 2026-08-24 13:44:44 -04:00
Brian Neumann-Fopiano e20bb86f40 f 2026-08-24 02:27:26 -04:00
Brian Neumann-Fopiano fe5651f854 d
d
2026-08-23 13:56:23 -04:00
Brian Neumann-Fopiano dce7af1955 object cleanup 2026-08-22 11:29:37 -04:00
Brian Neumann-Fopiano 770240e4f7 Merge pull request #1249 from VolmitSoftware/unification
Iris 4.0 unification: pure-JVM core + SPI with Bukkit/Fabric/Forge/NeoForge
adapters, art.arcane package layout, and the Gradle Groovy build.

Conflicts resolved in favour of unification for every path. The six commits
master gained after the fork point are all superseded there:
  - CraftEngine data provider (#1227): present, ported to CraftEngine 26.7.3
  - BlockProperty ofDouble/ofLong: present
  - 1.21.11 ambient-particle and ultrawarm dripstone datapack fixes: present
  - libs.versions.toml craftengine entries and the momirealms repo: present
  - build.gradle.kts / core/build.gradle.kts: replaced by the .gradle build

Merged tree is identical to origin/unification.
2026-08-21 23:30:51 -04:00
Robert Stoneking fe684083e7 Adding myself and fixing Windows Tests. 2026-08-21 20:59:26 -06:00
Brian Neumann-Fopiano 34092da6be d 2026-08-21 02:03:24 -04:00
Brian Neumann-Fopiano f43df6c232 d 2026-08-20 15:04:25 -04:00
Brian Neumann-Fopiano 8eaafa3c69 Object fixes / PS 2026-08-20 11:49:39 -04:00
Brian Neumann-Fopiano 5cacf6d435 MV 2026-08-20 10:59:17 -04:00
Brian Neumann-Fopiano fec655def4 ddd 2026-08-19 22:21:25 -04:00
Brian Neumann-Fopiano 41b2b53ac8 dwa 2026-08-19 19:36:03 -04:00
Brian Neumann-Fopiano 4adef4576f adding mroe of this 2026-08-18 11:52:33 -04:00
Brian Neumann-Fopiano 03746468ec COntent 2026-08-16 23:53:42 -04:00
Brian Neumann-Fopiano a1cef36f10 a 2026-08-15 23:02:50 -04:00
Brian Neumann-Fopiano 7691d2df53 dwa 2026-08-15 22:32:40 -04:00
Brian Neumann-Fopiano fc0fdf4ce4 d 2026-08-15 21:32:00 -04:00
Brian Neumann-Fopiano ef2e1b8f58 Modded adapters 2026-08-15 15:58:26 -04:00
Brian Neumann-Fopiano 8297e49ed2 Fix Iris replacement 2026-08-15 15:58:07 -04:00
Brian Neumann-Fopiano 4568b3fff1 chunk granularity 2026-08-15 15:58:00 -04:00
Brian Neumann-Fopiano 33638eadd7 height-bound samples 2026-08-15 15:57:54 -04:00
Brian Neumann-Fopiano a7baa1c1b9 nukkit artifacts 2026-08-15 15:57:46 -04:00
Brian Neumann-Fopiano d196ffe853 stabilize pregeneration 2026-08-15 15:57:36 -04:00
Brian Neumann-Fopiano 4535bf3eed probe 2026-08-15 15:57:30 -04:00
Brian Neumann-Fopiano 59f33bd921 probe- 2026-08-15 15:57:20 -04:00
Brian Neumann-Fopiano 57072d5bcd Correct Java 26.2 Iris world lifecycle 2026-08-15 11:27:46 -04:00
Brian Neumann-Fopiano 32beca236c ddd 2026-08-13 16:54:37 -04:00
Brian Neumann-Fopiano 1586b4bd1a dwa 2026-08-13 12:38:08 -04:00
Brian Neumann-Fopiano 14b6280668 Full audit fixpass: 93 defect fixes, 14 perf wins, vestigial cleanup 2026-08-13 01:01:01 -04:00
Brian Neumann-Fopiano 13e02ecd2f s 2026-08-12 16:31:27 -04:00
Brian Neumann-Fopiano c59592e448 rmd 2026-08-12 14:14:48 -04:00
Brian Neumann-Fopiano 365205ad0a d 2026-08-12 13:52:16 -04:00
Brian Neumann-Fopiano 12b97b7994 Fixes 2026-08-12 00:42:33 -04:00
Brian Neumann-Fopiano 8a189195ad d 2026-08-11 20:42:37 -04:00
Brian Neumann-Fopiano d9860782ef oops 2026-08-11 20:41:41 -04:00
Brian Neumann-Fopiano 623a02025c content 2026-08-11 20:35:27 -04:00
Brian Neumann-Fopiano 82290a3090 fix 2026-08-11 14:54:53 -04:00
Brian Neumann-Fopiano dfae45663a Fixes 2026-08-10 17:32:44 -04:00
Brian Neumann-Fopiano 998a5c9f5f dwa
(removed ai file from when i generated docs)
2026-08-10 15:48:22 -04:00
Brian Neumann-Fopiano ebfe278b3b Docvks 2026-08-10 15:47:26 -04:00
Brian Neumann-Fopiano 506787f51a Updated Docs, and Cortections 2026-08-08 00:29:48 -06:00
Brian Neumann-Fopiano c40e1cf152 Scary CI Builds. 2026-08-07 16:39:34 -06:00
Brian Neumann-Fopiano 68b842d5dc First Test MV 2026-08-07 14:04:17 -06:00
Brian Neumann-Fopiano 42b8db4856 BS 2026-08-06 16:23:56 -06:00
Brian Neumann-Fopiano dda5ea397e ADA 2026-08-06 07:41:58 -06:00
Brian Neumann-Fopiano 967c082372 d 2026-08-05 23:39:54 -06:00
Brian Neumann-Fopiano ce8c4ff578 d
d
2026-08-05 15:45:08 -06:00
Brian Neumann-Fopiano 70d355621c Temp vag 2026-08-05 14:30:07 -06:00
Brian Neumann-Fopiano a66cba6418 dwa 2026-08-05 02:28:14 -06:00
Brian Neumann-Fopiano cd217c05f9 Vwop 2026-08-05 01:16:30 -06:00
Brian Neumann-Fopiano aaccbacf32 dwa 2026-08-04 11:13:16 -06:00
Brian Neumann-Fopiano 10b2363226 ada 2026-08-02 04:16:06 -04:00
Brian Neumann-Fopiano a8217b42e9 F 2026-08-01 22:39:09 -04:00
Brian Neumann-Fopiano 324cf9e095 🧹 2026-07-30 12:31:39 -04:00
Brian Neumann-Fopiano 0afbfcb7a2 Changes 2026-07-30 02:36:15 -04:00
Brian Neumann-Fopiano 7ad4909518 Fix Iris entity spawn delegation 2026-07-29 18:10:55 -04:00
Brian Neumann-Fopiano 74741ac83e d 2026-07-27 13:46:24 -05:00
Brian Neumann-Fopiano d5a55ccfcf API 2026-07-26 12:12:49 -05:00
Brian Neumann-Fopiano de25b7d7ab d 2026-07-22 21:29:24 -05:00
Brian Neumann-Fopiano 9e1e202f15 dwa 2026-07-22 14:56:50 -05:00
Brian Neumann-Fopiano f6f06c1ab9 d 2026-07-21 12:00:17 -04:00
Brian Neumann-Fopiano d76dadec30 🔥 2026-07-18 21:48:52 -04:00
Brian Neumann-Fopiano 1d194e880b f 2026-07-18 19:16:14 -04:00
Brian Neumann-Fopiano ad41b6795c f 2026-07-18 18:12:04 -04:00
Brian Neumann-Fopiano 0193f75daa Fixes 2026-07-16 19:38:53 -04:00
Brian Neumann-Fopiano e8dd4e1127 iggy 2026-07-16 18:18:23 -04:00
Brian Neumann-Fopiano 699247b1de Vwoop 2026-07-16 12:26:01 -04:00
Brian Neumann-Fopiano 55b7460b9a s 2026-07-14 11:44:00 -04:00
Brian Neumann-Fopiano 45230c0689 dwa 2026-07-13 21:33:20 -04:00
Brian Neumann-Fopiano 4dea984d77 O-o .o-o. o-O 2026-07-12 20:53:51 -04:00
Brian Neumann-Fopiano cd05fd1bca World Changes 2026-07-12 17:06:30 -04:00
Brian Neumann-Fopiano e42ab6fae6 dswa 2026-07-11 19:28:40 -04:00
Brian Neumann-Fopiano 402bcb04fe dd 2026-07-10 04:05:24 -04:00
Brian Neumann-Fopiano 6111b5670a f 2026-07-09 18:34:16 -04:00
Brian Neumann-Fopiano 0a7a531646 Dont ask 2026-07-09 13:54:59 -04:00
Brian Neumann-Fopiano d1fec88769 d 2026-07-08 17:55:14 -04:00
Brian Neumann-Fopiano 848e056bb8 b 2026-07-05 10:16:15 -04:00
Brian Neumann-Fopiano 6b64624233 Fabric fix color my b 2026-07-04 17:42:50 -04:00
Brian Neumann-Fopiano d1cc83a40b fixes 2026-07-04 10:35:34 -04:00
Brian Neumann-Fopiano 927c48f8c7 Fixes 2026-07-03 21:37:08 -04:00
Brian Neumann-Fopiano 29ff3f01a7 Lovely 2026-07-03 17:25:58 -04:00
Brian Neumann-Fopiano a8e46e3590 iggy 2026-07-03 15:10:16 -04:00
Brian Neumann-Fopiano 75e8c22268 ff 2026-06-26 14:01:20 -04:00
Brian Neumann-Fopiano f777ea6b18 few more changes 2026-06-17 19:18:44 -04:00
Brian Neumann-Fopiano 8fb627f9c1 changes :) 2026-06-16 01:35:25 -04:00
Brian Neumann-Fopiano 3cfbcbf8ed Perf pass 2 2026-06-15 13:12:18 -04:00
Brian Neumann-Fopiano 79e80bef33 bump1 2026-06-14 13:52:57 -04:00
Brian Neumann-Fopiano 796d81e58b d 2026-06-14 01:21:11 -04:00
Brian Neumann-Fopiano b0a5d97e9c d 2026-06-13 22:03:58 -04:00
Brian Neumann-Fopiano ad6732812e d
d
2026-06-13 17:07:27 -04:00
Aidan Aeternum 28c4941941 v+ 2026-06-12 21:10:21 -04:00
Aidan Aeternum d452f10fd9 Merge pull request #1241 from VolmitSoftware/dev
3.9.2
2026-06-12 21:09:46 -04:00
Brian Neumann-Fopiano f482b8ef7e laDEDA 2026-06-12 15:52:01 -04:00
Brian Neumann-Fopiano e2f6061f98 Patches 2026-06-12 06:58:32 -04:00
Brian Neumann-Fopiano b98a341dc7 Cleanup 2026-06-12 02:54:34 -04:00
Brian Neumann-Fopiano 261a850af4 Remove Old Gource
(i can just reinstall it later)
2026-06-12 02:48:59 -04:00
Brian Neumann-Fopiano 516537db72 Forge Adapter 2026-06-12 02:48:22 -04:00
Brian Neumann-Fopiano 921a98f210 NeoForge Adapter 2026-06-12 02:48:14 -04:00
Brian Neumann-Fopiano 2bac948edb Fabric Parity Worldgen 2026-06-12 02:48:05 -04:00
Brian Neumann-Fopiano 26d9115904 Fabric Tooling 2026-06-12 02:47:54 -04:00
Brian Neumann-Fopiano 763ff90a61 Headless Pipeline 2026-06-12 02:47:42 -04:00
Brian Neumann-Fopiano f32094a404 Headless Generation 2026-06-12 02:47:32 -04:00
Brian Neumann-Fopiano 6b24105640 World Manager Seam 2026-06-12 02:47:20 -04:00
Brian Neumann-Fopiano a3d7269a99 Headless Classload 2026-06-12 02:47:10 -04:00
Brian Neumann-Fopiano e1196b0364 Vector Currency 2026-06-12 02:47:02 -04:00
Brian Neumann-Fopiano 4c462ba78a Resolution Currency 2026-06-12 02:46:46 -04:00
Brian Neumann-Fopiano 65d4abe5c0 Forge Skeleton 2026-06-12 02:46:34 -04:00
Brian Neumann-Fopiano 5142c048c7 Plugin Eviction 2026-06-12 02:46:22 -04:00
Brian Neumann-Fopiano 88c87175b3 Eviction Seams 2026-06-12 02:46:13 -04:00
Brian Neumann-Fopiano 56a0c75a39 Host Bridge 2026-06-12 02:46:13 -04:00
Brian Neumann-Fopiano 704c94aab4 Periphery Facades 2026-06-12 02:45:37 -04:00
Brian Neumann-Fopiano b5d08b6203 Service Facade 2026-06-12 02:45:18 -04:00
Brian Neumann-Fopiano b584cb4193 Logging Facade 2026-06-12 02:45:06 -04:00
Brian Neumann-Fopiano af3035088a Sealed Seam 2026-06-12 02:44:54 -04:00
Brian Neumann-Fopiano df888e5516 Entity Seam 2026-06-12 02:44:42 -04:00
Brian Neumann-Fopiano ca10edef0c Biome Writer 2026-06-12 02:44:29 -04:00
Brian Neumann-Fopiano 986c7f12cd Structure Hooks 2026-06-12 02:44:18 -04:00
Brian Neumann-Fopiano 6c5e991051 Engine Tier 3 2026-06-12 02:44:05 -04:00
Brian Neumann-Fopiano 1a834ed6f6 Serial Gate 2026-06-12 02:43:45 -04:00
Brian Neumann-Fopiano c9929e0880 Engine Slimming 2026-06-12 02:43:30 -04:00
Brian Neumann-Fopiano 3063801a3a Purity Ratchet 2026-06-12 02:43:15 -04:00
Brian Neumann-Fopiano 13002b04df World Ops 2026-06-12 02:43:02 -04:00
Brian Neumann-Fopiano 1c9f9d92bb Neutral Pipeline 2026-06-12 02:42:26 -04:00
Brian Neumann-Fopiano 4568f0b4b6 Currency Seams 2026-06-12 02:42:13 -04:00
Brian Neumann-Fopiano 1c08eb23b2 Bukkit SPI 2026-06-12 02:41:47 -04:00
Brian Neumann-Fopiano 7e224325f3 Restructure into cross-platform monorepo: spi module, adapters tree, buildAll 2026-06-12 02:41:06 -04:00
Brian Neumann-Fopiano a1ff3b2c55 Merge remote-tracking branch 'origin/unification' into unification 2026-06-11 12:42:57 -04:00
Brian Neumann-Fopiano abfc122171 SMID 2026-06-11 12:42:47 -04:00
Brian Neumann-Fopiano 6a68884ce9 Make world generation fully deterministic; add golden-hash verification harness
Generation now produces bit-identical output across repeated runs, server
restarts, and parallel generation (golden-hash verified, 289/289 chunks).

Engine fixes:
- MatterGenerator: components flagged complete before generateLayer finished
  (raiseFlagUnchecked), letting concurrent neighbors consume half-written
  mantle data; switched to completion-locked raiseFlagSuspend
- MantleObjectComponent: procedural objects (trees/ruins/formations/coral/
  fungi/crystals) were absent from computeRadius, so canopies crossed chunk
  borders without ordering guarantees; variant footprints now counted
- MantleCarvingComponent: carve profile ordering depended on IdentityHashMap
  iteration and hashCode sort ties; replaced with first-seen column-scan
  order and creation-sequence tiebreaks
- IrisObject/IrisPostModifier: shared BlockData instances mutated in place
  (vine facing x3, auto-waterlog x2, leaf persistence); clone before mutate
- IrisObject: slope-following placement mutated the shared pack-defined
  rotation config; now builds a local rotation on a copied placement
- IrisObjectPlacement: surface-warp CNG seeded from first-caller chunk RNG;
  now seeded from the engine seed manager
- GenerationCacheWarmer: engine init pre-bakes placement/decorator/ore/
  procedural caches in sorted order, removing lazy first-touch ordering
- IrisProceduralPlacement: expose getVariantObjects for radius computation

Verification harness:
- /iris dev goldenhash: buffered chunk generation (no world writes), SHA-256
  per-chunk block+biome digests, golden capture/verify with per-chunk
  mismatch reporting, auto-diagnosis (repeat-gen + mantle-reset comparison,
  block-level diffs), deep per-chunk blockstate dumps, threads/deep params,
  full mantle reset (saveAll + tectonic file wipe) for reproducible runs
- IrisEngineSVC: mantle trim/unload suspended while a goldenhash scan runs
- MantleCarvingComponentTop2BlendTest: sequence-tiebreak regression test
2026-06-11 12:40:21 -04:00
Brian Neumann-Fopiano c5a2a57822 SMID 2026-06-10 23:42:12 -04:00
Brian Neumann-Fopiano 58d24ca55e dwa 2026-06-10 21:21:18 -04:00
Brian Neumann-Fopiano c6c02a9040 f 2026-06-10 13:52:09 -04:00
Brian Neumann-Fopiano a530a03459 nms patches 2026-06-10 09:00:23 -04:00
Brian Neumann-Fopiano a82b8f4a9c dwa 2026-06-09 20:14:27 -04:00
Brian Neumann-Fopiano cf2e7df298 c 2026-06-08 00:20:23 -04:00
Brian Neumann-Fopiano d20b3af9b9 addons 2026-06-07 15:23:41 -04:00
Brian Neumann-Fopiano 01d98d2141 Procedurality 2026-06-07 01:55:17 -04:00
Brian Neumann-Fopiano febe930db0 dwa 2026-06-07 00:04:20 -04:00
Brian Neumann-Fopiano 5c832ba128 ignore 2026-06-06 18:39:57 -04:00
Brian Neumann-Fopiano 03f9e233da No more. memory leak in pregen 2026-06-03 19:08:32 -04:00
Brian Neumann-Fopiano 11efae428f x 2026-06-03 01:21:14 -04:00
Brian Neumann-Fopiano 1a6fff35e8 fff 2026-05-31 19:40:17 -04:00
Brian Neumann-Fopiano 540a416bc9 Good points 2026-05-31 07:51:24 -04:00
Brian Neumann-Fopiano 7eb6e87a81 Perf Pass 1 2026-05-30 22:39:51 -04:00
Brian Neumann-Fopiano 9ff1c502ef dwa 2026-05-30 01:27:53 -04:00
Brian Neumann-Fopiano 08c6f7ddf9 lmao 2026-05-29 23:52:58 -04:00
Brian Neumann-Fopiano 4bc1913f03 redesign for Structures 2026-05-29 19:54:10 -04:00
Brian Neumann-Fopiano 821cc027db fix 2026-04-23 22:44:52 -04:00
Brian Neumann-Fopiano 1968ef2f2c Wip floaters 2026-04-23 21:29:23 -04:00
Brian Neumann-Fopiano ce29b70618 Content 2026-04-23 16:38:32 -04:00
Brian Neumann-Fopiano 3d128b70a7 RedoDeco 2026-04-22 19:23:50 -04:00
Brian Neumann-Fopiano 23fad24fb7 Whoops 2026-04-22 16:33:25 -04:00
Brian Neumann-Fopiano 6df718e6ca floatt 2026-04-22 09:42:45 -04:00
Brian Neumann-Fopiano 787c728060 Amending / removing all structures will redesign later im over this 2026-04-20 18:20:53 -04:00
Brian Neumann-Fopiano 88bbce82fe Object Studio and Plausibilizer 1.0 2026-04-18 14:50:36 -04:00
Brian Neumann-Fopiano e6a8351e57 Bulk changes and fixes
namely the chunk issue, and objects not wantingto place on cave tops.
2026-04-17 22:28:35 -04:00
Brian Neumann-Fopiano b82472d521 Loads 2026-04-17 16:14:49 -04:00
Brian Neumann-Fopiano 8c05f1bf1d polish 2026-04-15 16:35:49 -04:00
Brian Neumann-Fopiano 6a5616abae d 2026-04-15 15:52:18 -04:00
Brian Neumann-Fopiano 6096c39192 OptiPass3 2026-04-15 15:13:35 -04:00
Brian Neumann-Fopiano b42d0e4710 OptiPass 2
Smart Sampling and stuff
2026-04-15 13:26:52 -04:00
Brian Neumann-Fopiano dcb3306197 Opti pass 1 2026-04-15 12:50:12 -04:00
Brian Neumann-Fopiano 9a231b2bcf Studio FIxes 2026-04-15 09:45:02 -04:00
Brian Neumann-Fopiano aa706d027b WIP Building for latest.
Still Quantizing, WOrking on that next
also removed prebake
2026-04-15 09:05:41 -04:00
Brian Neumann-Fopiano 568fb07f66 gr 2026-04-13 17:06:29 -04:00
Julian Krings e7d89cadd8 fix compile 2026-04-12 11:58:33 +02:00
Julian Krings cddd6b16d6 add CraftEngine data provider (#1227) 2026-04-12 11:55:36 +02:00
Julian Krings b2ac4e7f93 fix invalid json for ultrawarm dimensions on 1.21.11 2026-04-12 11:55:36 +02:00
Julian Krings c2bb32459f fix ambient particles breaking datapack generation on 1.21.11 2026-04-12 11:55:35 +02:00
Brian Neumann-Fopiano bf207b7062 speed pass 2026-02-24 03:57:07 -05:00
Brian Neumann-Fopiano 18d4dce1db dwa 2026-02-22 23:25:01 -05:00
Brian Neumann-Fopiano 651dfa247e perfpass 2026-02-22 09:58:33 -05:00
Brian Neumann-Fopiano 5b1ff6d2f7 ugh 2026-02-22 09:08:31 -05:00
Brian Neumann-Fopiano 589baafc28 kts gone 2026-02-22 08:59:50 -05:00
Brian Neumann-Fopiano 130073989d Fix Shafts 2026-02-22 07:09:59 -05:00
Brian Neumann-Fopiano 2ca8cc7ad3 f 2026-02-21 18:56:10 -05:00
Brian Neumann-Fopiano 9f35855599 dwa 2026-02-21 04:14:18 -05:00
Brian Neumann-Fopiano 5110a33857 forgor 2026-02-21 03:50:43 -05:00
Brian Neumann-Fopiano a38e87cce3 f 2026-02-21 03:49:10 -05:00
Brian Neumann-Fopiano 1d09cd5f0f dwa 2026-02-21 03:37:44 -05:00
Brian Neumann-Fopiano 72c891ce5b dwa 2026-02-20 23:17:17 -05:00
Brian Neumann-Fopiano 3964185a81 Working 2026-02-20 17:53:11 -05:00
Brian Neumann-Fopiano afc5f67bc2 f 2026-02-19 22:35:29 -05:00
Brian Neumann-Fopiano 1c72d6410f f 2026-02-19 19:50:29 -05:00
Brian Neumann-Fopiano c2c612eb35 d 2026-02-19 16:49:54 -05:00
Brian Neumann-Fopiano 415c2f5837 JIGGY! 2026-02-19 04:09:06 -05:00
Brian Neumann-Fopiano b491d9efd0 im not getting Jiggy with it 2026-02-19 02:02:31 -05:00
Brian Neumann-Fopiano d643461e2e f 2026-02-19 01:23:08 -05:00
Brian Neumann-Fopiano 1f41e195cf p1 2026-02-18 23:44:23 -05:00
Brian Neumann-Fopiano 7b9280083f f 2026-02-18 23:43:02 -05:00
Brian Neumann-Fopiano 73525c170d Noise Pass 2026-02-18 23:10:49 -05:00
Brian Neumann-Fopiano 16c861a781 f 2026-02-18 20:56:30 -05:00
Brian Neumann-Fopiano d6bd5ec82f d 2026-02-18 20:24:25 -05:00
Brian Neumann-Fopiano 1829fa3eb1 d
d
2026-02-18 15:18:21 -05:00
Brian Neumann-Fopiano 8d215e3911 V 2026-02-18 15:08:55 -05:00
Brian Neumann-Fopiano ba819e4899 canvas checks 2026-02-17 22:44:01 -05:00
Brian Neumann-Fopiano 05d79b6d40 Create std 2026-02-17 06:18:55 -05:00
Brian Neumann-Fopiano cf64fbb730 Waow 2026-02-17 02:54:34 -05:00
Brian Neumann-Fopiano 43a65135b1 WIP - Dont compile 2026-02-17 00:34:34 -05:00
Brian Neumann-Fopiano 440c1bec6c "Folia" 2026-02-16 06:07:25 -05:00
Brian Neumann-Fopiano 0fd8bcc488 f 2026-02-15 17:43:28 -05:00
Brian Neumann-Fopiano bb1c0b8f68 FIXES 2026-02-15 04:49:23 -05:00
Brian Neumann-Fopiano fc25acdea2 CMD 2026-02-14 23:55:01 -05:00
Brian Neumann-Fopiano 0aa6f6d523 d 2026-02-14 19:58:45 -05:00
Brian Neumann-Fopiano a79c1a7954 ignore, just tracking info 2026-02-14 18:11:43 -05:00
Brian Neumann-Fopiano 7885762cd7 shakey shakey 2026-02-14 05:44:10 -05:00
Brian Neumann-Fopiano 86ed3f0095 OOPS
kinda need this
2026-02-14 04:52:30 -05:00
Brian Neumann-Fopiano 99fcc8fd03 WIP 2026-02-14 01:56:05 -05:00
Brian Neumann-Fopiano 41888e33f6 Fixing fallbacks for macos Folk
Compiles fine, just stricter defaults
2026-02-11 00:01:23 -05:00
Aidan Aeternum 25fa2553e5 v+ 2026-02-02 03:28:49 -05:00
Aidan Aeternum 86f78baecf Merge pull request #1236 from VolmitSoftware/dev
3.9.1
2026-02-02 03:28:30 -05:00
Julian Krings c31158578f fix datapack generation on 1.21.11 2026-01-29 16:40:33 +01:00
Aidan Aeternum 4e86d7d634 v+ 2026-01-27 20:09:25 -05:00
Aidan Aeternum 62fe29cf34 Merge pull request #1235 from VolmitSoftware/dev
3.9.0
2026-01-27 20:08:36 -05:00
Julian Krings a3bcea4f3e increase memory assumption per region 2026-01-26 21:37:29 +01:00
Julian Krings 7befce1084 initial 1.21.11 support 2026-01-23 11:46:15 +01:00
Aidan Aeternum 351a1aa495 v+ 2026-01-15 02:50:30 -05:00
Aidan Aeternum d2cb8a9032 Merge pull request #1234 from VolmitSoftware/dev
3.8.2
2026-01-15 02:50:03 -05:00
Julian Krings 509715087c fix typo 2026-01-13 15:11:21 +01:00
Julian Krings 12527ecdd8 improve cachedChunks lookup in the mantle writer 2026-01-05 14:58:58 +01:00
Julian Krings 01a2999e03 decrease locking for mantle generation 2026-01-05 14:58:09 +01:00
Julian Krings dbafe84fa5 make context dereference method fail-safe 2026-01-02 14:12:44 +01:00
Julian Krings 40b020fc5d improve matter generator once again 2026-01-02 14:10:35 +01:00
Julian Krings d15f7d62d1 cleanup async world ticker 2026-01-02 14:10:35 +01:00
Julian Krings f9c062c794 improve mantle cleanup to prevent thread starvation 2026-01-02 14:10:34 +01:00
Aidan Aeternum 6a89b8bd06 v+ 2026-01-02 06:17:00 -05:00
Aidan Aeternum 194fcb2ea7 Merge pull request #1232 from VolmitSoftware/dev
3.8.1
2026-01-02 06:16:28 -05:00
Julian Krings 3b68f855b2 get back some speed by loading four mantle regions into cache at once 2025-12-31 13:38:42 +01:00
Julian Krings cfbf68d37a add check for headless environments to the edit command 2025-12-31 13:01:18 +01:00
Julian Krings 9999f3e429 fix chunk artifacts 2025-12-31 13:00:45 +01:00
Julian Krings 97fa0562a4 fix custom block data 2025-12-23 22:51:41 +01:00
Julian Krings 3abe671851 suppress unchecked cast warning for the ChunkedDataCache 2025-12-18 18:53:59 +01:00
Julian Krings e164a3bb5c Merge branch 'feat/safeguard' into dev
# Conflicts:
#	build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-12-18 18:44:36 +01:00
Julian Krings 4dfb4441e4 fix buildscript 2025-12-18 14:23:51 +01:00
Julian Krings e686f67453 add command to fix offsets of all objects 2025-12-18 14:23:35 +01:00
Julian Krings e2eed4812a ignore empty objects for shrinkwrap 2025-12-18 14:22:40 +01:00
Julian Krings 1737833bfe fix legacy object parsing 2025-12-18 14:22:21 +01:00
piexel a4c5f46c37 Fix /iris object convert for .schem files with 128+ block types 2025-12-18 11:25:38 +01:00
piexel 025fa833c4 Fix Grass won't place on moss blocks 2025-12-18 11:21:16 +01:00
Julian Krings 98cc82cc3d Fix jigsaw editor bricking when opening a piece a missing object 2025-12-18 11:13:43 +01:00
Julian Krings 298365f588 fix studio open not working when not in spectator 2025-12-18 11:06:09 +01:00
Julian Krings 90e5720e2e improve buildscript 2025-12-18 11:05:28 +01:00
Julian Krings 7cd43791f4 fix offset with shrinkwrap 2025-12-18 11:05:14 +01:00
Julian Krings a251d192ad fix loot for objects underground 2025-12-18 11:00:04 +01:00
Julian Krings abfff28f43 fix redstone not rotating 2025-12-18 10:59:57 +01:00
Julian Krings fbdae4c928 improve overwriting of the regenerated chunks 2025-12-18 10:59:32 +01:00
Aidan Aeternum 93ca26e368 v+ 2025-12-17 12:12:25 -05:00
Aidan Aeternum 8c1db1c223 Merge pull request #1231 from VolmitSoftware/dev
3.8.0
2025-12-17 12:11:28 -05:00
Julian Krings 123708601f improve pregen cache 2025-12-01 16:01:54 +01:00
Julian Krings b5ab4968ba fix compile 2025-11-30 15:36:36 +01:00
Julian Krings 93ae6037bd whoops forgot to disable craftbukkit relocations on <=1.20.4 2025-11-29 11:49:15 +01:00
Julian Krings dd8e487a3b fix tile entity serialization on paper 1.21.6+ servers 2025-11-29 11:13:30 +01:00
Julian Krings d3c8377a12 allow switching between buildtools and userdev 2025-11-29 11:12:16 +01:00
Julian Krings 84f3687c0c update compat for iron bars 2025-11-27 16:09:31 +01:00
Julian Krings ed1e1f1181 whoops forgot those providers 2025-11-26 21:33:03 +01:00
Julian Krings 528c97f367 replace IrisCustomData class with a proxy to keep full access to the base BlockData 2025-11-26 21:32:13 +01:00
Julian Krings f7a459f3bc initial 1.21.9/10 bindings 2025-11-26 21:25:05 +01:00
Julian Krings cd179b4321 reintroduce native threads for the updater as a setting and cleanup 2025-11-26 20:55:48 +01:00
Julian Krings 6373dbb1b8 potential optimization for the noise cache 2025-11-21 16:21:54 +01:00
Julian Krings 0b0797f876 minor optimization for chunk updater 2025-11-21 16:21:31 +01:00
Julian Krings 446acefc91 minor optimization for the mantle writer 2025-11-21 16:21:02 +01:00
Julian Krings 7a44e555b2 rename noise cache size setting and decrease default value 2025-11-21 16:20:32 +01:00
Julian Krings 57d4c2935c add cache sizes to engine status 2025-11-21 16:16:15 +01:00
Julian Krings 234fb1b0c4 also include parent class for schema generation 2025-11-08 00:58:12 +01:00
Julian Krings 0882b5acc4 use parent shared classloader to reflect intelij behavior 2025-11-06 22:32:28 +01:00
Julian Krings 18da26e1fa minor performance improvement 2025-11-06 22:27:14 +01:00
Julian Krings 65aa95f2a5 fix wrong radius when marking mantle chunks as completed 2025-11-06 22:25:37 +01:00
Julian Krings 5330ddc4ec fix deleting object ids with mantle cleanup 2025-11-06 22:20:30 +01:00
Julian Krings a7fdd37569 fix studio loot command 2025-11-06 22:16:27 +01:00
Julian Krings a226fea9e2 fix regen command 2025-11-06 16:22:02 +01:00
Julian Krings 8cea165a29 even more performance improvements 2025-11-06 15:29:03 +01:00
Julian Krings 1d7cba184c fix linear palette not growing correctly 2025-11-06 14:41:39 +01:00
Julian Krings 4ca7ea3911 minor speed improvements 2025-11-01 22:36:25 +01:00
Julian Krings ea5919def2 stop writing first access each time the engine is opened 2025-10-30 16:40:35 +01:00
Julian Krings be35e49302 use coroutines for mantle generation 2025-10-30 16:40:08 +01:00
Julian Krings aadd03990a optimize data palette for mantle slices 2025-10-27 19:53:47 +01:00
Julian Krings 38a579453d optimize noise cache 2025-10-26 13:49:03 +01:00
Julian Krings 0bf5da2ca1 optimize object maps 2025-10-26 13:48:11 +01:00
Julian Krings 317848692e improve regen speed 2025-10-06 14:12:01 +02:00
Julian Krings 22118de9e9 fix object smart bore 2025-10-06 14:10:37 +02:00
Julian Krings d7039d120b fix place commands causing unwanted block updates 2025-10-06 13:00:00 +02:00
Julian Krings 979ee4e7d8 switch hashing algorithm for objects once more 2025-10-05 21:46:12 +02:00
Julian Krings f68d45bd30 dynamically resolve snippet classes 2025-10-05 00:21:38 +02:00
Julian Krings b86d7f303e restructure the shared kts classloader to be more consistent 2025-10-05 00:20:55 +02:00
Julian Krings c573843314 fix kts dependency resolver 2025-10-05 00:17:57 +02:00
Julian Krings 51a7bef18e whoops forgot escaping the path 2025-10-04 17:19:11 +02:00
Julian Krings 0e237aa1ad fix generated build.gradle.kts on external dives on windows 2025-10-04 13:41:48 +02:00
Julian Krings b46c413f6b make gradle setup print to console on failure 2025-10-04 13:40:47 +02:00
Julian Krings f3ef1ca2ae fix typo in preprocessors description 2025-10-04 13:40:07 +02:00
Julian Krings 703e61dd54 fix preprocessors not applying reliably 2025-10-04 13:39:33 +02:00
Aidan Aeternum e1ec6b7827 v+ 2025-10-03 16:08:05 -04:00
Aidan Aeternum f94292fdac Merge pull request #1228 from VolmitSoftware/dev
3.7.11
2025-10-03 16:07:47 -04:00
Julian Krings 7d153bf985 optimize objects to avoid hash collision 2025-10-02 15:25:37 +02:00
Julian Krings f85f15ed02 fix converter for complex schematic 2025-10-02 11:43:16 +02:00
Julian Krings 867686eced fix deserialisation of unversioned mantle plates 2025-10-01 12:57:38 +02:00
Julian Krings 526efd3ae1 restructure safeguard 2025-09-29 15:45:56 +02:00
Aidan Aeternum 9d796bd2a0 v+ 2025-09-27 07:34:58 -04:00
Aidan Aeternum 1a9a5d80ad Merge pull request #1223 from VolmitSoftware/dev
3.7.10
2025-09-27 07:34:31 -04:00
Julian Krings c5c7f9bdc5 fix minor issue with nms tools 2025-09-22 18:10:34 +02:00
Julian Krings 01a421b732 add file extensions to the script property descriptions 2025-09-21 22:35:34 +02:00
Julian Krings ae92bcf194 add kts hook for chunk updates 2025-09-21 22:30:48 +02:00
Julian Krings 7e7933858b suppress json syntax exceptions from being reported to sentry 2025-09-21 15:47:29 +02:00
Julian Krings 9c073ecbcb fix infinite loop due to writing the gradle.kts 2025-09-21 14:01:59 +02:00
Julian Krings f4617c1996 add tabcompletion for mythic mobs mob stacks 2025-09-21 12:51:59 +02:00
Julian Krings 21a2e4feef fix hotloading when changing kts 2025-09-21 12:13:57 +02:00
Julian Krings 258d0d3aaa make sure that the pack is installed correctly 2025-09-21 00:15:34 +02:00
Julian Krings 27b2fd0823 show other packs data again 2025-09-20 23:31:33 +02:00
Julian Krings 0524adb0df fix compile 2025-09-20 23:05:24 +02:00
Julian Krings 3981b0976d cleanup command framework and fix random locator fails 2025-09-20 23:05:16 +02:00
Julian Krings b5811cae08 cleanup IrisData usage 2025-09-20 23:04:11 +02:00
Julian Krings c998fd1fd9 move additional build data in its own constants class 2025-09-20 17:25:20 +02:00
Julian Krings a7d874d37f use scheduled thread pool for scoreboard svc to prevent freezes 2025-09-20 15:27:49 +02:00
Julian Krings 7c41f86fb3 add slope condition for slabs 2025-09-20 14:28:33 +02:00
Julian Krings e5e0561d5a cleanup world creator 2025-09-17 18:02:45 +02:00
Julian Krings d50cdfec3e cleanup pack installation 2025-09-17 18:02:20 +02:00
Julian Krings 00997c1902 return if missing datapack entries were found 2025-09-17 17:36:18 +02:00
Julian Krings 3095a92522 add experimental setting to force place custom blocks as early as possible 2025-09-10 23:06:46 +02:00
Julian Krings fca309dec7 move the loading block data message into debug 2025-09-10 17:02:51 +02:00
Julian Krings 2793ed1035 send block update after placing itemsadder blocks 2025-09-10 17:01:29 +02:00
Julian Krings e5908285af remove old worlds from cache before retrieving 2025-09-10 16:15:01 +02:00
Julian Krings a8ee321eb8 Allow placing datapack structures in Iris worlds (#1225)
* Allow placing datapack structures in Iris worlds

* command for generating configs for datapack structures

* remove the sub dir from the snippet key
2025-09-07 18:36:08 +02:00
Julian Krings aa14242b54 another mantle fix 2025-09-07 16:50:23 +02:00
Julian Krings f6968269b4 add toggle to offset noise types fixing seeds 2025-09-06 14:35:25 +02:00
Julian Krings 0d0251e2f1 add toggle to change rarity algorithm 2025-09-06 14:21:52 +02:00
Julian Krings 81b8fb02ae replace rarity calculation due to it breaking at more than two values 2025-09-06 14:21:52 +02:00
Julian Krings 77842489e5 add command for checking the effective rarity of regions 2025-09-06 14:21:52 +02:00
Julian Krings eda1f59d3a fix broken cave floors 2025-09-05 17:02:36 +02:00
Julian Krings 5418868559 fix hotloading mantle components 2025-09-05 16:56:56 +02:00
Julian Krings 1d81daafbb add option to control the noise threshold for cave shapes 2025-09-05 16:06:53 +02:00
Julian Krings 609a3585c1 use flat maven repository for classpath context 2025-09-05 16:04:49 +02:00
Julian Krings 571dde608c add shared class loader support for script dependencies 2025-09-05 12:21:28 +02:00
Julian Krings 3a13f5a7c1 fix dependency resolve failing on new intelij versions 2025-09-04 23:07:37 +02:00
Julian Krings e5654b74d4 Auto Completion for block properties (#1222) 2025-09-04 18:06:00 +02:00
Julian Krings a75738dd7a minor noise optimizations 2025-09-04 16:13:41 +02:00
Julian Krings 003be1f88b use mantle chunk for deposit modifier 2025-09-04 13:56:05 +02:00
Julian Krings 1eaafae20d move caffeine cache executors into a virtual thread executor 2025-09-04 13:55:34 +02:00
Julian Krings 79088c0305 fix hyperlock not releasing on exceptions 2025-09-04 13:55:03 +02:00
Julian Krings 9e147774bd revert mantle change to hopefully fix deadlock 2025-09-04 13:54:28 +02:00
Julian Krings e51b632c8f Merge pull request #1221 from VolmitSoftware/feat/kts
Kotlin script engine
2025-09-04 13:43:08 +02:00
Julian Krings 1aa64c9a02 add hook for custom engine modes 2025-09-03 17:02:39 +02:00
Julian Krings 1e148d8fcd minor code cleanup 2025-09-03 13:32:08 +02:00
Julian Krings f63cabd8b8 fix mantle flag serialization 2025-09-03 12:56:02 +02:00
Julian Krings 176d3a5f9f remove old system property 2025-09-02 22:14:59 +02:00
Julian Krings eb184983de fix json property description on vscode 2025-09-02 22:09:32 +02:00
Julian Krings d1c307865d properly generate gradle wrapper for kts tab completion 2025-09-02 22:05:11 +02:00
Julian Krings 4a26b8b34f fix compile 2025-09-02 21:57:34 +02:00
Julian Krings 33fd01c3ac Merge branch 'dev' into feat/kts
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java
#	core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java
#	core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java
#	core/src/main/java/com/volmit/iris/util/mantle/Mantle.java
#	core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java
2025-09-02 12:47:23 +02:00
Julian Krings 34874080e7 cleanup some array types for json schema generation 2025-09-02 12:28:54 +02:00
Julian Krings 43131ed8f6 make jar scanner also load child classes 2025-09-02 12:27:26 +02:00
Julian Krings 9ea425aee4 isolate io stuff in its own MultiBurst 2025-09-02 12:27:01 +02:00
Aidan Aeternum 709f05c1a5 v+ 2025-08-29 18:23:28 -04:00
Aidan Aeternum ec74add5de Merge pull request #1219 from VolmitSoftware/dev
3.7.2
2025-08-29 18:23:02 -04:00
Julian Krings d5b706764a isolate jigsaw component from objects 2025-08-27 16:12:12 +02:00
Julian Krings ca4c205a4a decrease wait times in mantle components 2025-08-27 16:08:49 +02:00
Julian Krings 7b9c2ae6ad minor optimizations 2025-08-27 12:34:52 +02:00
Julian Krings d0e9d44152 fix random null pointer in the resource loader 2025-08-27 00:33:59 +02:00
Julian Krings 2cdffaae33 include stronghold in mantle radius calc 2025-08-27 00:33:30 +02:00
Julian Krings d4a8beac95 fix more structures not being placed properly 2025-08-27 00:32:58 +02:00
Julian Krings 0e0e4075d8 fix more unsafe mantle operations 2025-08-27 00:32:34 +02:00
Julian Krings 9c492a2e66 save dev command for mantle panics 2025-08-26 17:46:27 +02:00
Julian Krings 96bf83684c add the shutdown hook on plugin enable instead of disable to prevent issues 2025-08-26 17:45:20 +02:00
Julian Krings 25ea9ae62d make regen respect multicore setting 2025-08-26 17:23:49 +02:00
Julian Krings 7938c150dd fix the DataContainer for the last time 2025-08-26 17:23:33 +02:00
Julian Krings a7b4bf3ff2 hopefully fix the DataContainer this time 2025-08-25 20:53:52 +02:00
Julian Krings 693a05f2cb move the updater command out of dev 2025-08-25 19:44:03 +02:00
Julian Krings e48cbe1f69 performance optimizations and add two experimental options for further optimizations 2025-08-24 22:33:35 +02:00
Julian Krings 558f6fa8dd Merge pull request #1218 from VolmitSoftware/fix/mantle
Mantle Fixes
2025-08-24 22:24:09 +02:00
Julian Krings 67b29cc363 cleanup palette and fix incorrect bit resizing 2025-08-24 18:54:44 +02:00
Julian Krings 4702534f9c add size check to prevent further corruption in case of read offset 2025-08-24 17:21:21 +02:00
Julian Krings 29390c5e0a fix potential issues in the CountingDataInputStream 2025-08-24 17:20:01 +02:00
Julian Krings e8bfce469d change the trimming cycle to make sure one step happens per second 2025-08-24 17:19:50 +02:00
Julian Krings 770e2f47a2 centralize file channels to prevent concurrent access 2025-08-24 17:19:39 +02:00
Julian Krings 747e7b3330 optimize the chunk updater 2025-08-21 17:27:07 +02:00
Julian Krings 8662f3b47a update ItemsAdder namespaces on load data 2025-08-21 13:50:21 +02:00
Aidan Aeternum 6e738e69e7 v+ 2025-08-21 02:40:05 -04:00
Aidan Aeternum 4bf14c83e1 Merge pull request #1215 from VolmitSoftware/dev
3.7.1
2025-08-21 02:39:19 -04:00
Julian Krings 837674c295 update sentry link 2025-08-19 18:00:22 +02:00
Julian Krings e5f3bbd69e make ItemsAdderDataProvider more reliable when adding items & blocks 2025-08-12 22:18:56 +02:00
Julian Krings 2885a39299 cleanup script engine 2025-08-09 00:00:31 +02:00
Julian Krings bd1b06c761 make studio map zoom in on the cursor target 2025-08-08 21:40:36 +02:00
Julian Krings 3e1143112a fix offset in studio map and some other issues 2025-08-08 21:38:26 +02:00
Julian Krings 834b214fbf add setting to control deposits replacing bedrock 2025-08-07 17:11:38 +02:00
Julian Krings 768e569400 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java
#	gradle/libs.versions.toml
2025-08-02 23:32:08 +02:00
Julian Krings a5d04333dd whoops messed up the structure slice 2025-08-02 23:19:11 +02:00
Julian Krings ebe8da0fd5 update slimjar for built-in spigot helper 2025-08-02 21:22:44 +02:00
Julian Krings 5eb25f3977 add property to use registry keys for where applicable
disabled by default for now
2025-08-02 00:01:33 +02:00
Julian Krings a5bca0a9bb fix not reading enum values correctly 2025-08-01 13:06:25 +02:00
Julian Krings a82882c1c1 move local maven repo into iris directory 2025-08-01 12:35:08 +02:00
Julian Krings 05193bd0d9 make depencency resolvers more predictable 2025-08-01 04:51:58 +02:00
Julian Krings 96efc15c36 fix local dependencies not resolving properly 2025-07-31 17:08:50 +02:00
Julian Krings 67f456cf53 cleanup script engine 2025-07-31 00:16:23 +02:00
Julian Krings 58b1bd115f update adventure api 2025-07-30 19:18:57 +02:00
Julian Krings 8ddc8abdb9 fix resolving dependencies in scripts 2025-07-30 16:54:19 +02:00
Julian Krings 12c2c71739 Merge branch 'refs/heads/dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java
#	core/src/main/java/com/volmit/iris/util/misc/SlimJar.java
#	gradle/libs.versions.toml
2025-07-29 13:34:25 +02:00
Julian Krings 8705ca6e47 save structure placed at block 2025-07-28 21:44:27 +02:00
Julian Krings a610d0a7a9 remove unnecessary classpath injections 2025-07-28 20:53:01 +02:00
Julian Krings 0648cfd3fa add message for promotion of world to main world 2025-07-28 15:16:21 +02:00
Julian Krings fba9c17e3f fix not using relocated bukkit.yml correctly and remove duplicate code 2025-07-28 15:08:07 +02:00
Julian Krings 74128d58cf disable pack trimming for now 2025-07-28 15:06:34 +02:00
Julian Krings 70130e976d add getter method to IrisWorlds 2025-07-28 13:53:11 +02:00
Julian Krings 501c426302 fix slimjar error for new projects 2025-07-28 12:31:07 +02:00
Julian Krings 8471f15bc8 make world creation more failsafe 2025-07-27 23:23:04 +02:00
Julian Krings c4539441a0 fix datapack generation ignoring worlds when updating from <3.7.0 2025-07-27 23:22:40 +02:00
Julian Krings 472a98da16 update slimjar 2025-07-27 18:43:33 +02:00
Julian Krings 76a6f1465a update slimjar 2025-07-26 14:31:45 +02:00
Julian Krings b0ca0e3617 whoops messed up the extension retrieval 2025-07-25 13:21:16 +02:00
Julian Krings 08b9058c8f make git commit retrieval fail safe 2025-07-25 13:07:13 +02:00
Julian Krings 556bef6e43 fix more unsafe mantle chunk accesses 2025-07-25 12:44:01 +02:00
Julian Krings 16a4d20c90 use grgit to resolve current commit 2025-07-23 18:06:52 +02:00
Julian Krings e5f2fee926 add info about used commit to sentry reports 2025-07-23 15:07:06 +02:00
Julian Krings 3949468a60 inject into the library loader to make sure libraries can always be loaded 2025-07-23 14:45:54 +02:00
Julian Krings fc54fcb7eb make itemsadder data provider standalone 2025-07-22 12:12:13 +02:00
Julian Krings 9d6e4e87d8 fix custom blocks not placing 2025-07-22 12:11:41 +02:00
Julian Krings f50e964d4f hopefully fix unsafe mantle chunk operations 2025-07-21 17:14:45 +02:00
Julian Krings 8262e52893 cleanup regen 2025-07-19 18:10:57 +02:00
Julian Krings 1c41dc69ce Merge pull request #1213 from VolmitSoftware/feat/caves
Feat/caves
2025-07-19 17:59:32 +02:00
Julian Krings dee46a4284 cleanup object mask rotation 2025-07-19 17:48:58 +02:00
Julian Krings a8892b04ef bump nms for 1.21.8 support 2025-07-19 12:59:50 +02:00
Julian Krings d49f7d7821 add parameter to the create command to make it your main world 2025-07-13 17:43:11 +02:00
Julian Krings 106a3834ab fix iris create command failing 2025-07-13 17:42:10 +02:00
Julian Krings 387e8adfe2 ignore errors when scanning for external data providers 2025-07-12 18:31:39 +02:00
Julian Krings 9ade90d9ca make the mythic mobs link a data provider and make adding custom mob plugins easier 2025-07-12 15:51:49 +02:00
Julian Krings 3d2392843a update maven core to fix dependency resolution issues 2025-07-12 14:18:35 +02:00
Julian Krings a9891e819a v+ 2025-07-12 12:51:04 +02:00
Julian Krings 02c13a9391 Merge pull request #1211 from VolmitSoftware/dev
3.7.0
2025-07-12 12:42:38 +02:00
Julian Krings f6590c26e7 fix download failing for dimensions with snippets 2025-07-11 13:07:20 +02:00
Julian Krings 64bb81626c update overworld pack 2025-07-11 12:54:16 +02:00
Julian Krings 343dc429d5 fix null pointer when failing to load a dimension 2025-07-10 19:20:46 +02:00
Julian Krings 8f5f44bc96 open vscode after studio world was created 2025-07-10 18:24:50 +02:00
Julian Krings 6964b99744 fix reading custom mantle flags from json 2025-07-10 16:41:46 +02:00
Julian Krings 11cfd85f6a allow creation of custom mantle flags for custom mantle components 2025-07-10 16:11:02 +02:00
Julian Krings c5416f54fa add script hook for custom noise 2025-07-09 23:40:04 +02:00
Julian Krings 94c5782490 move loading message to debug 2025-07-09 18:49:55 +02:00
Julian Krings cc49b0f540 hopefully fix eta 2025-07-09 17:37:58 +02:00
Julian Krings 1bc6192c8b merge kts engine into this codebase 2025-07-09 15:16:11 +02:00
Julian Krings 4b0766c097 remove bfs 2025-07-09 14:24:24 +02:00
Julian Krings ec5cb2d646 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java
2025-07-09 14:23:53 +02:00
Julian Krings 0edaeeec99 massive cleanup of maven repos and update dependencies 2025-07-08 23:40:10 +02:00
Julian Krings a0543bbbf2 relocate slimjar 2025-07-08 19:59:45 +02:00
Julian Krings 0b1af51227 add option to disable specific mantle components for testing 2025-07-08 19:52:09 +02:00
Julian Krings 03c5998c02 generate a dummy region for standalone focus biomes 2025-07-08 19:08:12 +02:00
Julian Krings 6b193f695a switch to slimjar 2025-07-08 18:46:59 +02:00
Julian Krings 0a30881f87 add jaxen to runtime dependencies 2025-07-08 16:08:30 +02:00
Julian Krings c01a7def5d add iris data setup hook 2025-07-08 15:37:14 +02:00
Julian Krings 50db1d11a7 move script engine libraries into the iris plugin folder 2025-07-08 12:30:21 +02:00
Julian Krings 2e2ea8f1e4 use paper plugin loader to avoid maven central warning 2025-07-08 09:19:57 +02:00
Julian Krings badf108d56 Merge branch 'dev' into feat/kts
# Conflicts:
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java
#	core/src/main/java/com/volmit/iris/util/io/IO.java
#	core/src/main/resources/plugin.yml
2025-07-08 00:36:30 +02:00
Julian Krings f9888d19a5 whoops forgot to update publish task 2025-07-08 00:24:03 +02:00
Julian Krings fbc5cee300 if it still doesn't work, idk what will 2025-07-08 00:22:31 +02:00
Julian Krings 12777bc3f0 does this shit work now?? 2025-07-07 23:59:42 +02:00
Julian Krings 9324b1b5c0 fix api file generation 2025-07-07 23:42:30 +02:00
Julian Krings 2ecb555619 add publishing for the api artifact 2025-07-07 23:37:37 +02:00
Julian Krings 20c7891c2f add api generation task 2025-07-07 22:54:35 +02:00
Julian Krings c35c858eee add scripts for engine setup 2025-07-07 21:06:36 +02:00
Julian Krings cb93e78242 fix safeguard error 2025-07-07 13:18:31 +02:00
Julian Krings c9ed4519a8 remove multiverse core incompatibility warning (#1212) 2025-07-06 20:00:06 +02:00
Julian Krings 86d986dfbc fix deposits spawning in columns 2025-07-06 19:40:14 +02:00
Julian Krings 54402faea8 fix automatic deepslate variant replacement 2025-07-04 13:18:28 +02:00
Julian Krings 77b4253624 fix sentry safeguard info 2025-07-04 12:34:20 +02:00
Julian Krings 44af23ba2e add check for new dimension types 2025-07-04 00:06:55 +02:00
Julian Krings 4e8079e431 use ticket queue for pregen by default 2025-07-04 00:01:16 +02:00
Julian Krings 39f65d02bf 1.21.6/7 Support (#1210) 2025-07-03 23:58:31 +02:00
Julian Krings 8c025a9ba2 clear snippet lists on hotload 2025-07-03 23:45:27 +02:00
Julian Krings 7119fa8ef7 Merge pull request #1207 from VolmitSoftware/feat/byte_buddy
Feat/byte buddy
2025-07-03 23:24:30 +02:00
Julian Krings 6f0b2b6bba initialize generators for isolated focus biomes / regions 2025-06-27 12:21:59 +02:00
Julian Krings 0ae1334a57 fix snippet tab completion 2025-06-27 12:20:25 +02:00
Julian Krings 5d28563b7c implement generate surface for ores 2025-06-24 19:43:46 +02:00
Julian Krings cf8243a000 make caves more deterministic 2025-06-24 19:37:37 +02:00
Julian Krings cca0bed482 fix cave max size calculation 2025-06-24 16:53:26 +02:00
Julian Krings a802edc375 add some options for customizing caves 2025-06-23 17:16:45 +02:00
Julian Krings 3677931114 improve snippet finder 2025-06-23 15:51:53 +02:00
Julian Krings e38dae0a32 fix required properties for json schemas 2025-06-23 15:51:29 +02:00
Julian Krings d537459c5a Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle.kts
#	core/build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-23 14:24:43 +02:00
Julian Krings 67398174bb cache iris worlds in a map for datapack generation 2025-06-23 12:25:39 +02:00
Julian Krings 0a0f77ff58 fix splash being shown twice 2025-06-23 12:25:38 +02:00
Julian Krings e5cb4d82a3 Add KGenerators support (#1209) 2025-06-21 12:05:56 +02:00
Julian Krings 9d44ac0b47 fix caves being non-deterministic 2025-06-21 12:04:39 +02:00
Julian Krings 7f6d65a13e fix worm ignoring the breakSurface option 2025-06-21 12:01:06 +02:00
Julian Krings ad720f4aa2 fix worm using xStyle for y and z 2025-06-21 11:51:49 +02:00
Julian Krings 80548f753c isolate java agent to prevent class loader issues 2025-06-21 11:48:58 +02:00
Julian Krings c9c8a9e412 fix null pointers in pack trim method 2025-06-16 22:53:56 +02:00
Julian Krings d048c073ac disable trim for fallback pack download 2025-06-16 22:53:11 +02:00
Julian Krings 2929a1f0a7 update Script Engine to e08b6f893e 2025-06-16 22:44:15 +02:00
Julian Krings b6f9f68b9f add global preprocessor setting 2025-06-16 22:43:26 +02:00
Julian Krings da2dd42e28 invert suppressed error reporting for dev enviroments 2025-06-16 16:59:24 +02:00
Julian Krings 88360ef772 Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle.kts
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-15 23:00:39 +02:00
Julian Krings 73787e21d2 Merge branch 'dev' into feat/kts
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/Iris.java
#	core/src/main/java/com/volmit/iris/util/io/IO.java
2025-06-15 22:54:56 +02:00
Aidan Aeternum 840608a40f v+ 2025-06-14 16:49:22 -04:00
Aidan Aeternum fcedc35635 Merge pull request #1205 from VolmitSoftware/dev
3.6.11
2025-06-14 16:47:20 -04:00
Julian Krings 32d9a5e40a make sentry engine context hotload safe 2025-06-14 11:46:51 +02:00
Julian Krings 8df6253604 add safeguard info to sentry reports 2025-06-14 11:44:30 +02:00
Julian Krings 01b62c13b6 fix deleting mantle temp files before they are fully written 2025-06-14 11:18:14 +02:00
Julian Krings f32f73e65a disable error reporting in dev environment 2025-06-13 12:27:03 +02:00
Julian Krings 7b7118fe0d add id for servers hash of jvm name & version, processor info, memory size and plugins 2025-06-13 12:26:56 +02:00
Julian Krings 851ac18f0d implement custom conditions for mythic mobs (#1204) 2025-06-11 13:23:09 +02:00
Julian Krings 37be7ca847 don't send json and zip file closed exceptions to sentry 2025-06-11 13:14:28 +02:00
Julian Krings ed67b4d3c2 fix spawners not having entities due to using old format 2025-06-11 13:03:31 +02:00
Julian Krings b62ac875d5 cleanup gradle 2025-06-10 16:48:00 +02:00
Julian Krings 4c3f95b0e1 Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/Iris.java
2025-06-10 13:33:56 +02:00
Julian Krings 8712c8874c disable global pregen cache by default for now 2025-06-09 23:27:12 +02:00
Julian Krings ccc3bab8e0 allow carving recursion 2025-06-09 23:25:33 +02:00
Julian Krings 113f25dab8 more sentry context 2025-06-09 23:17:47 +02:00
Julian Krings cd80acdc7d fix IndexOutOfBoundsException when getting the selection from the wand 2025-06-09 19:50:50 +02:00
Julian Krings 9316ea9e5b fix npe when creating cuboids 2025-06-09 19:49:06 +02:00
Julian Krings e2a3f25dcb fix load in parallel method 2025-06-09 19:42:03 +02:00
Julian Krings 944cc19ebc use proper shuffling algorithm for the loot 2025-06-09 19:35:43 +02:00
Julian Krings 172e234514 fix divide by zero in the engine svc 2025-06-09 19:19:00 +02:00
Julian Krings f9dac8a3a1 fix compile while missing the sentry auth token 2025-06-09 18:40:43 +02:00
Julian Krings eefbbab7ee remove legacy chars before minimessage deserialization 2025-06-09 18:39:11 +02:00
Julian Krings 9cf567e1ff remove unused util to fix class not found exception 2025-06-09 13:47:24 +02:00
Julian Krings b8ee7561dd fix failing to create temp file when user deleted the temp directory 2025-06-09 13:44:57 +02:00
Julian Krings c0d17742e8 fix release task for sentry 2025-06-09 13:43:00 +02:00
Julian Krings a88d389e0f bump gradle wrapper to 8.14.2 and switch to kotlin dsl (#1203) 2025-06-09 12:45:27 +02:00
Julian Krings b341089996 v+ 2025-06-09 02:12:14 +02:00
Julian Krings 9cbfd5a10b Merge pull request #1202 from VolmitSoftware/dev
3.6.10
2025-06-09 02:11:22 +02:00
Julian Krings fdaf8ff9d3 relocate sentry to fix incompatibility with some plugins using a very old sentry version 2025-06-09 00:17:38 +02:00
Julian Krings e63d84c052 remove server name from reports 2025-06-09 00:16:58 +02:00
Julian Krings 0d103a934a fix ticking engine players in the wrong dimension 2025-06-08 12:43:56 +02:00
Julian Krings 8119207254 sentry changes
- info message on enabling
- add release tag
- disable uncaught exception handling to prevent reporting other plugins issues
2025-06-08 12:29:24 +02:00
Julian Krings b66e6d8335 correct sentry dsn 2025-06-07 14:00:22 +02:00
Julian Krings ce2b62f5ae add sentry release task 2025-06-06 18:37:11 +02:00
Julian Krings c767b6c8e8 check sentry env token as fallback 2025-06-06 18:36:41 +02:00
Julian Krings 67328d7d10 remove test exception for sentry 2025-06-06 17:35:13 +02:00
Julian Krings 329e136a66 implement opt-out auto reporting with sentry (#1201) 2025-06-06 17:33:16 +02:00
Julian Krings 52f87befa2 return noop pregen cache when the service is disabled and write pregen cache to temp file first then replace real one 2025-06-06 17:24:23 +02:00
Julian Krings 2ee22db072 fix mantle write failing on windows 2025-06-06 16:49:42 +02:00
Julian Krings cc27e87376 fix world height decreases deleting the whole chunk 2025-06-05 20:34:25 +02:00
Julian Krings abb1d9cd62 add bytebuddy binding for 1.21.5 2025-06-05 20:26:16 +02:00
Julian Krings e0ad029c3d Merge branch 'dev' into feat/byte_buddy
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java
2025-06-05 19:27:01 +02:00
Julian Krings 5705caa1ba fix benchmarking not disabling properly 2025-06-04 20:20:48 +02:00
Julian Krings a56cd4c268 use block data use for slice 2025-06-04 16:32:29 +02:00
Julian Krings 48cc6bb49c improve mantle writer speeds 2025-06-04 15:03:00 +02:00
Aidan Aeternum 635ee02459 v+ 2025-06-02 18:48:01 -04:00
Aidan Aeternum 2c7b7c8c91 Merge pull request #1199 from VolmitSoftware/dev
3.8.9
2025-06-02 18:47:38 -04:00
Julian Krings ad0662be54 initial 1.21.5 bindings 2025-06-02 18:18:17 +02:00
Julian Krings 835c8422f1 calculate spawn location async 2025-06-02 17:42:36 +02:00
Julian Krings 2c60192de3 use scheduled thread pool instead of loopers for the EngineSVC 2025-06-02 17:29:38 +02:00
Julian Krings adb7188eb9 refactor mantle cleanup 2025-06-02 17:29:30 +02:00
Julian Krings 2436ebb857 Merge pull request #1198 from VolmitSoftware/feat/versioned_mantle 2025-06-02 17:28:58 +02:00
Julian Krings ad85a0bbd1 implement marker exhaustionChance 2025-05-29 23:17:23 +02:00
Julian Krings 22ac9ebf47 update nexo api to release 1.6.0 2025-05-29 14:18:30 +02:00
Julian Krings 3cb9585dd8 fix pregenerator not closing when a new one is started 2025-05-28 21:48:41 +02:00
Julian Krings f4d1177c51 fix cave fluid ignoring fluid palette 2025-05-28 12:57:11 +02:00
Julian Krings b132862a60 update last use for in use tectonic plates 2025-05-27 22:50:30 +02:00
Julian Krings 2452a2f633 cleanup of the mantle trimmer / EngineSVC 2025-05-27 16:23:02 +02:00
Julian Krings f0476fea9b implement version header for tectonic plates 2025-05-26 20:54:12 +02:00
Julian Krings 90c6457d37 write to plates to a temp file first and then move it into the mantle dir 2025-05-26 17:29:15 +02:00
Julian Krings 61301ffd4d close engines using a shutdown hook 2025-05-26 17:29:15 +02:00
Julian Krings e42317139d fix engines not closing on server stop 2025-05-26 17:29:15 +02:00
Julian Krings f68600464b remove redundant method from NMSBinding1X 2025-05-25 18:12:48 +02:00
Julian Krings 97ddfd309b fix updater not working 2025-05-21 13:05:07 +02:00
Aidan Aeternum 5d42c5cae0 v+ 2025-05-20 12:32:04 -04:00
Aidan Aeternum 52fecb48d8 Merge pull request #1196 from VolmitSoftware/dev
3.6.8
2025-05-20 12:31:35 -04:00
Julian Krings ce29dc98c3 fix spawn markers not being removed 2025-05-16 12:41:31 +02:00
Julian Krings 463e3d9658 Merge pull request #1193 from VolmitSoftware/feat/faster_pregen
Feat/faster pregen
2025-05-16 12:29:41 +02:00
Julian Krings 9152b25d51 fix pack hash calculation 2025-05-16 12:22:35 +02:00
Julian Krings bb9c72e414 Partially Merge pull request #1192 from dan28000/Iris 2025-05-16 12:11:46 +02:00
Julian Krings 976648340e add pregen method that doesn't use waiting threads 2025-05-16 12:02:38 +02:00
Julian Krings 5958bcb22e update Script Engine to 41396a77d5 2025-04-28 21:40:44 +02:00
Julian Krings 8eb35aa8be update Script Engine to b5e2cc6e 2025-04-28 21:24:54 +02:00
Julian Krings 49ce84a9e9 Merge branch 'dev' into feat/faster_pregen
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java
2025-04-27 13:56:28 +02:00
Julian Krings 6724b0f4c5 minor cleanup and improved safeguard for missing dimension types 2025-04-25 21:18:33 +02:00
Julian Krings e72abc8c39 generate json schemas for intelij 2025-04-24 16:00:23 +02:00
Julian Krings 0a2f35dd8d update the scripting engine and fix cached file extension 2025-04-23 21:24:14 +02:00
Julian Krings c597c55c2c replace rhino scripting engine with kotlin scripting 2025-04-23 14:21:09 +02:00
Aidan Aeternum f93995e152 v+ 2025-04-21 20:27:43 -04:00
Aidan Aeternum 7a5a2e5909 Merge pull request #1191 from VolmitSoftware/dev
3.6.7
2025-04-21 20:27:18 -04:00
dan28000 e15d31a0ed would be nice to display the right number 2025-04-21 22:59:29 +02:00
dan28000 09cdd61a68 fix method
when having relocated worlds folder this method would fail making warning appear
2025-04-21 20:45:20 +02:00
dan28000 0575cd85c8 delete old stuff
delete legacy command
2025-04-21 20:44:38 +02:00
Julian Krings 2008975a8a expose most of the dimension type options 2025-04-17 17:30:29 +02:00
Julian Krings aad1d3815a Merge branch 'dev' into feat/byte_buddy 2025-04-16 23:18:49 +02:00
Julian Krings 86c64f99e9 filter out non iris pack directories in allPacks stream 2025-04-16 23:01:57 +02:00
Julian Krings 6577f4a5de rough safeguard for missing dimension type 2025-04-16 22:02:42 +02:00
Julian Krings 3415e7c7af implement complete world height isolation 2025-04-16 21:52:30 +02:00
Pixel f1c72974fd Cleanup (#1186)
* Remove server benchmark command

* Moved lazy pregen to dev
2025-04-15 15:53:50 +02:00
Julian Krings 26e2e20840 Feat/pregen cache (#1190)
Add cache to pregen to skip already generated chunks faster
2025-04-14 21:26:39 +02:00
Julian Krings c00dcf205b fix deadlock when closing pregen method while using modified concurrency 2025-04-14 21:25:31 +02:00
Julian Krings a10a784c3b add max concurrency setting for pregen 2025-04-14 21:25:31 +02:00
Julian Krings f99cc61042 add setting to use a virtual thread executor instead of MultiBurst for async pregen 2025-04-14 21:25:31 +02:00
Julian Krings d2a1e5cc1e fix duplicate & redundant purpur recommendations 2025-04-14 21:23:52 +02:00
Julian Krings 3e2c0fa025 Decrease MSPT impact of throwing ender eyes in Iris worlds 2025-04-14 21:23:37 +02:00
Julian Krings 9c151abac7 replace world load context injection with bytecode injections 2025-04-10 16:26:58 +02:00
Aidan Aeternum ab4770400e v+ 2025-04-10 04:49:32 -04:00
Aidan Aeternum 1a810d5d62 Merge pull request #1184 from VolmitSoftware/dev
3.6.6
2025-04-10 04:49:02 -04:00
Julian Krings 536d20bca7 remove context injection on world init 2025-04-09 12:16:03 +02:00
Julian Krings 9a691ac5b4 setup runServer for all supported versions 2025-04-09 10:57:35 +02:00
Julian Krings 71078a20a9 Don't inject world load context for main worlds to prevent incompatibilities 2025-04-08 21:15:19 +02:00
Julian Krings 566fca2b52 Add info message for failed increase of worker threads 2025-04-07 19:00:06 +02:00
Julian Krings 74e2576ca2 Prevent saving the iris level stems 2025-04-06 16:31:35 +02:00
Julian Krings fb0bc112e3 increase worker threads on paper servers during pregen 2025-03-30 14:31:11 +02:00
Julian Krings 407e51378c fix applying x offset to z coords in Spiraler 2025-03-27 15:02:47 +01:00
Julian Krings c468eb1ab1 make pregen use block radius as input 2025-03-27 15:02:47 +01:00
Aidan Aeternum bdb7cc61e5 v+ 2025-03-26 15:41:35 -04:00
Aidan Aeternum e8f9e841c4 Merge pull request #1181 from VolmitSoftware/dev
3.6.5
2025-03-26 15:41:06 -04:00
Julian Krings 1b1b9d97b7 update overworld pack to 31020 2025-03-25 19:15:47 +01:00
Julian Krings 24355064ff add safeguard for missing dimension types to prevent world corruption 2025-03-25 19:14:20 +01:00
Julian Krings 06a45056d9 use flat level source instead of trying to get the levelstems 2025-03-22 12:38:25 +01:00
Aidan Aeternum dfe4894be7 v+ 2025-03-18 16:40:33 -04:00
Aidan Aeternum 8eb2287ec0 Merge pull request #1178 from VolmitSoftware/dev
3.6.4
2025-03-18 16:34:22 -04:00
Julian Krings c4f0722614 improve datapack setup speed 2025-03-18 16:18:37 +01:00
Julian Krings 7fa1484b21 fix levelstem injection not working for main worlds 2025-03-18 16:18:37 +01:00
Julian Krings 1c5eb8b910 automatically update vanilla dimension type if present in Iris datapack 2025-03-18 16:18:37 +01:00
Julian Krings 94bf530d93 add setting to enable changing to the vanilla dimension height values 2025-03-18 16:18:37 +01:00
Julian Krings 686ae57b5b switch from world preset to direct level stems 2025-03-18 16:18:37 +01:00
Julian Krings a911685aaf Fix world gen datapack incompatibilities 2025-03-18 16:18:37 +01:00
Aidan Aeternum 6899761ca9 v+ 2025-03-06 17:06:34 -05:00
Aidan Aeternum a58958fd62 Merge pull request #1176 from VolmitSoftware/dev
3.6.3
2025-03-06 17:04:57 -05:00
Julian Krings 307f3c9158 implement version specific overworld tag (#1175) 2025-03-06 15:57:30 +01:00
Julian Krings 4f275c2e06 Merge pull request #1174 from tavaresjoshua8/master
Fix: Nexo 1.0
2025-03-06 15:55:54 +01:00
tavaresjoshua8 7e4e3f3cd8 Fix: Nexo 1.0 Release 2025-03-05 13:43:26 -07:00
Aidan Aeternum 84e5add564 v+ 2025-02-23 14:39:52 -05:00
Aidan Aeternum 4d4adbb76f Merge pull request #1172 from VolmitSoftware/dev
3.6.2
2025-02-23 14:39:29 -05:00
Julian Krings ff2f285784 isolate iris world height into it's own dimension types (#1171) 2025-02-20 23:41:19 +01:00
Aidan Aeternum 8b1636e78a v+ 2025-02-14 16:46:51 -05:00
Aidan Aeternum 3bdad10562 Merge pull request #1170 from VolmitSoftware/dev
3.6.1
2025-02-14 16:46:22 -05:00
Julian Krings ac03a977aa Mob spawning fixes (#1169)
* fix cooldown being 0 in most cases

* fix max entity count for spawners
2025-02-11 23:06:47 +01:00
Aidan Aeternum d7270f66e1 v+ 2025-02-06 16:16:11 -05:00
Aidan Aeternum b220b1bffa Merge pull request #1168 from VolmitSoftware/dev
3.6.0
2025-02-06 16:15:05 -05:00
Julian Krings 4796fe98cb fix modern tile states not applying properly 2025-02-05 15:20:34 +01:00
Julian Krings ece905ec6e fix registry lookup failing on some server versions 2025-02-04 21:11:08 +01:00
Julian Krings 53c9e7c04c drop 1.19.x support (#1167) 2025-02-04 13:04:48 +01:00
Julian Krings 29f6f52443 add noise based spread type (#1164) 2025-02-04 13:04:28 +01:00
Julian Krings a778cc51a6 Expression functions (#1165) 2025-02-04 13:03:49 +01:00
Julian Krings c6963d0cd3 fix world delete command crashing servers (#1166) 2025-02-04 13:03:33 +01:00
pixel 489844f61b Updated README.md to JDK21 2025-02-03 22:19:38 +01:00
Aidan Aeternum 4d1b0246ca v+ 2025-02-01 10:55:58 -05:00
Aidan Aeternum 13f3511fa8 Merge pull request #1159 from VolmitSoftware/dev
3.5.9
2025-02-01 10:54:00 -05:00
Julian Krings f6f2766315 fix ignoring some custom blocks (#1158) 2025-01-30 13:12:15 +01:00
Aidan Aeternum 56530a1245 v+ 2025-01-29 17:51:36 -05:00
Aidan Aeternum 210a1f29a7 Merge pull request #1156 from VolmitSoftware/dev
3.5.8
2025-01-29 15:51:16 -07:00
Julian Krings 847bf972ae improve performance related to custom blocks (#1155) 2025-01-27 15:22:10 +01:00
Aidan Aeternum e5d21fdf7e v+ 2025-01-25 09:48:00 -07:00
Aidan Aeternum 0b2fd3b358 v+ 2025-01-25 09:47:17 -07:00
Aidan Aeternum 10484d1226 Merge pull request #1153 from VolmitSoftware/dev
3.5.7
2025-01-25 09:46:58 -07:00
Julian Krings ce0092c52a Fix creation deadlock (#1154) 2025-01-25 14:54:21 +01:00
Julian Krings 474e033c2b Feat/1.21.4 (#1152) 2025-01-24 18:03:12 +01:00
Aidan Aeternum 62aad1f497 v+ 2025-01-23 14:46:15 -07:00
Aidan Aeternum 32b5157682 Update build.gradle 2025-01-23 14:44:53 -07:00
Aidan Aeternum 70717ea282 Merge pull request #1151 from VolmitSoftware/dev
3.5.6
2025-01-23 14:44:24 -07:00
Julian Krings 15975f108c Fix compile 2025-01-22 22:52:21 +01:00
Julian Krings 66c66e82c1 remove "smart" vanilla height (#1150) 2025-01-22 22:47:39 +01:00
Julian Krings 4f6da95d8e fix radius calculation skipping pieces (#1149) 2025-01-22 22:47:28 +01:00
Aidan Aeternum b37ccbdf01 v+ 2025-01-17 08:27:58 -05:00
Aidan Aeternum 30dbe0881a Merge pull request #1145 from VolmitSoftware/dev
3.5.5
2025-01-17 05:26:26 -08:00
Julian Krings 20ad4657a9 fix null pointer preventing the entity schema from generating (#1142) 2025-01-15 11:30:55 +01:00
Julian Krings d4986f42a6 Fix decorator determinism (#1144) 2025-01-15 11:30:34 +01:00
Aidan Aeternum 8df15c0c2d v+ 2025-01-12 14:12:43 -08:00
Aidan Aeternum 24e1c578c8 Merge pull request #1139 from VolmitSoftware/dev
3.5.4
2025-01-12 14:10:50 -08:00
Julian Krings 1c3bff7559 fix sculk veins not properly placing with decorators (#1137) 2025-01-12 15:37:10 +01:00
Julian Krings a09657b4d0 precalculate pack hash on engine setup (#1138) 2025-01-12 15:36:54 +01:00
Aidan Aeternum 910220d3ca v+ 2025-01-08 18:22:26 -08:00
Aidan Aeternum fc05c24e3a Merge pull request #1135 from VolmitSoftware/dev
3.5.3
2025-01-08 18:20:43 -08:00
Julian Krings e1a7e772cf fix floating objects (#1134) 2025-01-07 22:03:53 +01:00
Aidan Aeternum 3c6411c322 v+ 2025-01-07 11:22:52 -08:00
Aidan Aeternum 628761affa Merge pull request #1130 from VolmitSoftware/dev
3.5.2
2025-01-07 11:21:06 -08:00
Julian Krings fa7b0f68ff Merge pull request #1129 from VolmitSoftware/feat/deposits
Feat/deposits
2025-01-05 16:25:03 +01:00
Aidan Aeternum 487d0ac237 Merge pull request #1128 from VolmitSoftware/dev
3.5.1
2025-01-04 09:57:30 -08:00
Julian Krings e79e3fbe45 implement <1 deposit spawn chance 2025-01-04 03:46:08 +01:00
Julian Krings 23a0ab23aa fixes to deposit clump generation and placement 2025-01-04 03:41:55 +01:00
Julian Krings c78ffab948 fix deposit clump calculation 2025-01-03 00:33:49 +01:00
Julian Krings d58f497b71 v+ 2025-01-02 23:21:32 +01:00
Julian Krings 3284ab84c5 fix biome&region deposits being placed ^2 2025-01-02 23:15:27 +01:00
Julian Krings 5cf0ac9315 Merge pull request #1115 from VolmitSoftware/v3.4.3
V3.5.0
2025-01-02 19:37:22 +01:00
Julian Krings 4e4e820826 update overworld tag and make first setup use the tag rather than current codebase 2025-01-02 14:38:45 +01:00
Julian Krings cb6e4570f4 add fallback loot mode 2025-01-02 13:28:20 +01:00
Julian Krings fb59c7370d partially revert commit c93cb19563 2025-01-02 12:33:59 +01:00
Julian Krings c16e65f0a8 add force place option to IrisJigsawStructure 2025-01-02 12:33:59 +01:00
Julian Krings d9681edf62 relocate IrisLootEvent to the proper package 2025-01-02 12:33:59 +01:00
repix 520c156d5f Repix gradle update 2024-12-29 16:47:09 +01:00
Julian Krings 5c26ec2521 fix vanilla loottables not applying 2024-12-28 00:43:01 +01:00
Julian Krings d192e2b6d1 add tabcomplete for vanilla loottables and fix some multi version compat 2024-12-27 18:14:29 +01:00
Julian Krings 5b60b655ed some more optimizations 2024-12-26 17:54:02 +01:00
Julian Krings 97c7ac0528 some more bstats metrics 2024-12-26 11:22:06 +01:00
Julian Krings 090ff730a7 remove item type caching for schemas 2024-12-24 10:09:22 +01:00
Julian Krings c1f797e7c9 update bstats 2024-12-23 23:37:01 +01:00
Julian Krings 4a1a6b80a6 minor performance increase 2024-12-23 23:36:25 +01:00
Julian Krings e189b2389c add vanilla loottables 2024-12-22 16:39:41 +01:00
Julian Krings 3265447536 woops 2024-12-14 16:43:42 +01:00
Julian Krings 8c7c9f89e1 add missing nullability annotations to data providers 2024-12-14 16:42:01 +01:00
Julian Krings d7a991b9b3 fix NexoDataProvider 2024-12-14 16:40:04 +01:00
Julian Krings abf6c93f2e allow custom data provider registration 2024-12-14 16:39:41 +01:00
Julian Krings 1b76a66760 update iris core to jdk 21 2024-12-14 16:23:29 +01:00
RePixelatedMC c83ac67b47 New features 2024-12-05 10:28:04 +01:00
Julian Krings 1dca502a90 add more safety to mantle 2024-11-27 19:45:08 +01:00
Julian Krings cacef8c8fc add legacy tile data reader 2024-11-25 20:05:21 +01:00
Julian Krings 623fd45ef4 fixes to nexo support 2024-11-24 19:00:51 +01:00
Julian Krings 007b4b0b53 whoops 2024-11-23 21:09:54 +01:00
Julian Krings b6bacee095 replace Oraxen with Nexo support 2024-11-23 21:03:14 +01:00
Julian Krings d5251350a1 initial 1.21.3 support 2024-11-16 20:19:28 +01:00
Julian Krings 1f9c72d093 fix compile 2024-11-09 13:58:10 +01:00
Julian Krings a1495a10e5 updater fixes 2024-11-08 21:01:58 +01:00
Julian Krings 11ae48bea1 revert previous 2024-10-31 21:33:24 +01:00
Julian Krings b0f4b29a2d ehh 2024-10-31 21:05:07 +01:00
Julian Krings c38bb1cd01 stability improvements for the chunk updater 2024-10-30 15:59:31 +01:00
Julian Krings 7faa727bd2 midsave ChunkUpdater improvements 2024-10-29 22:12:22 +01:00
Julian Krings 9e40259ca2 fix LegacyTileData not placing 2024-10-17 19:18:11 +02:00
Julian Krings 94a7692735 add force place for stronghold 2024-10-17 18:48:17 +02:00
Julian Krings 8b803a87f0 rename CrucibleDataProvider to MythicCrucibleDataProvider to fit naming conventions 2024-10-17 11:28:50 +02:00
Julian Krings 3ae6e92eaf woops 2024-10-16 22:05:47 +02:00
Julian Krings 0f3c52a5aa cleanup CrucibleDataProvider and fix furniture support 2024-10-16 21:59:38 +02:00
Schroddinger 747be7aa09 MythicCrucible support. Currently under testing.
(cherry picked from commit 06d0f0748bfb3a69b663c2c3604b65f52cb4c5c2)
2024-10-16 21:37:54 +02:00
Brian Fopiano d651f204f8 Merge pull request #1117 from hUwUtao/master
make javadoc buildable...
2024-10-02 00:43:56 -04:00
stdpi 2bd3ac7a9b Merge branch 'VolmitSoftware:master' into master 2024-09-25 02:00:31 +07:00
Brian Neumann-Fopiano 5e437b34e3 Revert "Update LICENSE.md"
This reverts commit 6c6c9654c1.
2024-09-24 14:58:10 -04:00
stdpi a5ef89a128 add irisDev task to copy javadocs and sources jar 2024-09-22 17:35:00 +07:00
stdpi 558b8e4eca add more jar package (javadocJar sourcesJar) 2024-09-22 16:09:48 +07:00
stdpi 0a001b8a63 fix: javadoc tolerate bad html
and match encoding, ugh
2024-09-22 04:48:18 +07:00
Brian Fopiano 6c6c9654c1 Update LICENSE.md 2024-09-09 16:54:57 -04:00
RePixelatedMC e21fdf46e0 - New Noise color < need further testing >
- Iris schem converter
2024-09-05 21:57:40 +02:00
RePixelatedMC a2ff5f58ed Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3 2024-09-04 15:07:42 +02:00
RePixelatedMC b0eedee519 - Progress bar for /iris std dist
- Noted out that its in chunks for radius in /iris std dist
2024-09-04 15:07:30 +02:00
Julian Krings e101155a4c fix datapack particle rarity 2024-09-01 18:51:23 +02:00
Julian Krings 3c9bcc9bb0 add chunked cuboid iterator 2024-09-01 15:24:49 +02:00
RePixelatedMC 6763844030 back to debug 2024-08-31 18:29:43 +02:00
RePixelatedMC 08fa436885 better 2024-08-31 17:45:09 +02:00
RePixelatedMC df8fa79209 Merge remote-tracking branch 'origin/v3.4.3' into v3.4.3 2024-08-31 17:17:44 +02:00
RePixelatedMC 8041db4f40 - New draw system
- Crazy performance compared to the old system
2024-08-31 17:17:20 +02:00
Julian Krings 18e57e4097 add progressbar to object writing 2024-08-31 16:28:52 +02:00
Julian Krings cc584ba377 add progressbar to object saving 2024-08-31 16:01:25 +02:00
Julian Krings 0c92c20c65 ehm 2024-08-31 14:13:50 +02:00
Julian Krings ec8af56f0d woops 2024-08-28 20:30:43 +02:00
Julian Krings 79341bf562 update adventure api 2024-08-28 14:17:54 +02:00
Julian Krings 3f24d5c8e1 fix seed for structure placement 2024-08-27 15:55:02 +02:00
Julian Krings 66a1739666 v+ 2024-08-25 22:16:40 +02:00
Julian Krings 29007fdbfa fix walls not being rotated 2024-08-25 21:48:10 +02:00
Julian Krings be3e8ebd51 fix MMOItems support 2024-08-25 19:41:35 +02:00
Julian Krings 38ad345f85 potential fix for pregen deadlock 2024-08-25 19:35:49 +02:00
Julian Krings 414f46a08d Merge remote-tracking branch 'origin/master' into v3.4.3 2024-08-21 13:09:11 +02:00
repixelatedmc 9144606688 Biggest 1 line fix ever 2024-08-19 19:36:35 +02:00
Julian Krings effb0f5b91 fix biome colors 2024-08-18 17:02:34 +02:00
Julian Krings bb7bbb6379 fix legacy tile data loading from mantle 2024-08-17 19:01:27 +02:00
Julian Krings dafca8e9db woops 2024-08-17 12:29:08 +02:00
Julian Krings 41b7aec084 Merge branch 'fix_maps' into v3.4.3
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java
#	nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java
#	nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java
#	nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java
#	nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java
#	nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java
#	nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java
2024-08-17 12:25:23 +02:00
Julian Krings dfe1cce6de restructure LegacyTileData 2024-08-17 11:59:28 +02:00
Julian Krings fc793592f7 fix old object loading 2024-08-17 00:45:58 +02:00
Brian Neumann-Fopiano c3ac41f894 V+
lol
2024-08-15 12:58:01 -04:00
Julian Krings d0688782b1 fix getEngineData NullPointer 2024-08-15 18:47:27 +02:00
Julian Krings 25b41fe62c add 1.21.1 support 2024-08-15 18:42:23 +02:00
Julian Krings b468478fcb separate tile data into its own option in IrisBlockData 2024-08-15 18:00:18 +02:00
Julian Krings b6dc934198 fix getEngineData NullPointer 2024-08-15 17:12:56 +02:00
Julian Krings f58078e8a0 implement dynamic tile entities 2024-08-14 20:02:08 +02:00
Julian Krings b6457e47e6 fix async tile state 2024-08-13 21:35:17 +02:00
Julian Krings d275466e1e reformat BukkitChunkGenerator::onWorldInit 2024-08-13 20:25:54 +02:00
Julian Krings f9cb107728 catch getEngineData NullPointer 2024-08-13 20:25:34 +02:00
repixelatedmc 68ad206252 Eh yeah should do 2024-08-12 08:08:23 +02:00
Julian Krings 288bead792 fix stronghold locate using wrong seed 2024-08-11 12:03:47 +02:00
Julian Krings de670ddfd5 fix explorer/treasure map 2024-08-11 12:03:23 +02:00
Brian Neumann-Fopiano 79d6f34879 v+ 2024-08-03 14:42:07 -04:00
Julian Krings ceb6c15c97 tall_seagrass isnt sticking out of water anymore
by RePixelatedMC
2024-08-03 18:45:44 +02:00
Julian Krings 7cf43ad7ab fix world creation 2024-08-03 18:04:37 +02:00
Julian Krings ab3397a373 fix pack download command 2024-08-03 15:30:56 +02:00
Julian Krings 8e9f78e982 fix region zoom 2024-08-03 13:03:50 +02:00
Julian Krings 61bbfee640 Update README.md 2024-08-02 18:20:25 +02:00
Brian Neumann-Fopiano cf4796bd12 V+ 2024-07-31 18:45:13 -04:00
Julian Krings bd89d8a308 change version to show 1.21 compat 2024-08-01 00:20:30 +02:00
Julian Krings df2186d70f Merge pull request #1110 from VolmitSoftware/v1_21
1.21 Support
2024-07-31 23:52:10 +02:00
Julian Krings f9638e830f update NMSTools 2024-07-30 12:56:27 +02:00
Julian Krings cd55a7fed4 fix getBiomeColor for 1.19.2 2024-07-27 15:43:09 +02:00
Julian Krings fc890a5ba1 fix missing getBiomeColor method 2024-07-27 15:42:51 +02:00
Julian Krings 2ea54b7f2f 1.21 nms bindings 2024-07-24 19:59:01 +02:00
Brian Fopiano e90e3901f5 Merge pull request #1109 from VolmitSoftware/fixes
Fixes
2024-07-23 15:43:33 -04:00
Brian Fopiano 36d58f29ab Merge pull request #1108 from VolmitSoftware/better_nms
Better NMS
2024-07-23 15:43:21 -04:00
Brian Fopiano 864c7ae27c Merge pull request #1106 from VolmitDev/custom_data
improve custom block/item api to allow custom settings
2024-07-23 15:43:04 -04:00
Julian Krings 6d104a2d1c fix y-coordinate for getting biome 2024-07-23 20:05:05 +02:00
Julian Krings 89e754245d add maxTries to IrisLootTable to prevent crash 2024-07-21 16:00:02 +02:00
Julian Krings 4c68d99c6f fix returning black color in biomes without a color override 2024-07-21 15:50:45 +02:00
Julian Krings f8f0a65f0a add debug info to the updater 2024-07-21 15:25:31 +02:00
Julian Krings 6ff74639b5 use custom nms gradle plugin 2024-07-18 17:17:05 +02:00
Julian Krings af80f882da add slope condition to sear/shore/surface decorators 2024-07-09 16:25:07 +02:00
Julian Krings 84ce7372c3 implement dimension zoom options 2024-07-09 16:23:32 +02:00
Julian Krings e31d39c5f7 fix worldedit selection not updating 2024-07-09 16:19:56 +02:00
Julian Krings 2296c1368b fix WorldEditLink 2024-07-09 16:19:22 +02:00
Brian Fopiano 21d7d96dbc Merge pull request #1107 from RePixelatedMC/master 2024-07-05 00:46:54 -04:00
RePixelatedMC 956e511fe0 Merge remote-tracking branch 'origin/master' 2024-07-04 22:02:42 +02:00
RePixelatedMC e427887f8c Scary 2024-07-04 22:00:50 +02:00
Julian Krings 88ea43fbbe fix post processing for custom blocks not applying to objects 2024-06-28 18:14:52 +02:00
Julian Krings 29526a80a9 improve custom block/item api to allow custom settings 2024-06-25 13:35:34 +02:00
Julian Krings 48f901fc8c handle null pointer 2024-06-25 13:33:44 +02:00
Julian Krings 6c3f3dc889 update oraxen api 2024-06-24 13:17:11 +02:00
Julian Krings 743410d3ee add nms method to get biome color 2024-06-24 13:16:56 +02:00
Brian Neumann-Fopiano acccbb028f V+ 2024-06-18 21:32:35 -04:00
Julian Krings 8b31fdc780 Merge pull request #1105 from VolmitDev/jigsaw_dist
Fix jigsaw spawn distances
2024-06-17 17:07:47 +02:00
CrazyDev22 55017b9ac2 Revert "Merge branch 'mca' into jigsaw_dist"
This reverts commit 0288d35c85, reversing
changes made to 8b3577dd0b.
2024-06-16 14:25:47 +02:00
Julian Krings 0288d35c85 Merge branch 'mca' into jigsaw_dist 2024-06-16 14:23:49 +02:00
Julian Krings 8b3577dd0b make divisor used for generating missing jigsaw placement values changeable 2024-06-16 14:06:24 +02:00
Brian Neumann-Fopiano ced77ec70d V+ 2024-06-09 17:26:08 -04:00
Julian Krings e1e90f5c8a add toolchain resolver 2024-06-08 08:27:17 +02:00
RePixelatedMC dc3487583d Merge pull request #25 from VolmitSoftware/merge
merge
2024-06-05 09:53:26 +02:00
RePixelatedMC e1d73ebcd5 Merge branch 'mca' into merge 2024-06-05 09:53:13 +02:00
RePixelatedMC d7cf3b9500 no gui for now 2024-06-03 10:03:14 +02:00
Brian Fopiano 8ce4e3c6b2 Merge pull request #1104 from RePixelatedMC/master
small changes
2024-06-01 10:30:21 -04:00
RePixelatedMC 76f7d46b22 e 2024-06-01 16:26:21 +02:00
RePixelatedMC cfc52ed909 Tuning + Fixed server distro reading for 1.20.6+ 2024-06-01 16:21:57 +02:00
RePixelatedMC cee94d40e1 Merge branch 'master' of https://github.com/VolmitSoftware/Iris 2024-05-29 10:18:04 +02:00
RePixelatedMC cad7166cbe Merge pull request #24 from VolmitDev/fix_jigsaw
minor changes to jigsaw
2024-05-25 15:41:05 +02:00
Julian Krings c05fdbd0ec Merge pull request #1103 from VolmitDev/fix_jigsaw
minor changes to jigsaw placement
2024-05-25 15:39:07 +02:00
CrazyDev22 c9b26ebaff add better backwards compat 2024-05-25 15:34:51 +02:00
CrazyDev22 bee1973390 fix salt not being a long 2024-05-25 15:34:25 +02:00
RePixelatedMC 57efcab0b5 tall_seagrass isnt sticking out of water anymore 2024-05-22 16:55:07 +02:00
RePixelatedMC b1ab2b84f8 Fixed /iris region going of a chunk instead of x,z coords
Added fast edit commands
- /iris std edit biome is now supported and opens the vscode biome of where you are standing at same with region.
2024-05-22 15:34:45 +02:00
RePixelatedMC 02601e5ee4 Cleanup 2024-05-22 13:32:29 +02:00
RePixelatedMC 070479acf3 Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev 2024-05-22 13:29:16 +02:00
RePixelatedMC 6b2ba74237 Iris lets the user ingame know when unstable. 2024-05-22 13:28:56 +02:00
RePixelatedMC 619de29710 Merge pull request #23 from VolmitDev/mca
improve iris world protection
2024-05-21 16:39:21 +02:00
CrazyDev22 12556fa98f improve iris world protection 2024-05-21 16:38:28 +02:00
RePixelatedMC bef4c0497f Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev 2024-05-21 11:57:03 +02:00
RePixelatedMC 9193497d36 Merge pull request #22 from RePixelatedMC/v3.3
V3.3 x MCA?
2024-05-21 11:56:14 +02:00
RePixelatedMC ca6affbde9 Merge branch 'master' into v3.3 2024-05-21 11:48:39 +02:00
RePixelatedMC 81c40ce228 Merge branch 'master' of https://github.com/RePixelatedMC/Iris into PixelatedDev 2024-05-21 11:23:52 +02:00
RePixelatedMC 6ba1d571f2 Fallback type when mm not on server 2024-05-21 11:23:24 +02:00
RePixelatedMC b0663f9b6c Merge branch 'master' into PixelatedDev 2024-05-21 11:22:39 +02:00
RePixelatedMC 3ced6f3e9e Entity Fallback 2024-05-20 14:46:11 +02:00
RePixelatedMC 3146d61efe Merge pull request #19 from VolmitDev/mca
Sync
2024-05-20 14:04:32 +02:00
CrazyDev22 a967b6af85 replace datapack system with a dynamic one 2024-05-20 06:41:38 +02:00
Brian Fopiano db3bc74364 Merge pull request #1092 from VolmitDev/fix_jigsaw
Jigsaw fixes
2024-05-19 18:21:11 -04:00
CrazyDev22 eb19d9a846 Merge branch 'refs/heads/master' into mca
# Conflicts:
#	build.gradle
#	core/src/main/java/com/volmit/iris/core/ServerConfigurator.java
#	core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java
#	core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java
2024-05-19 20:48:13 +02:00
Julian Krings 96dd299de7 Merge pull request #1102 from VolmitDev/v1_20_R4
move datapack upgrade command under dev
2024-05-19 19:11:43 +02:00
CrazyDev22 aee0806aa8 move datapack upgrade command under dev 2024-05-19 19:10:53 +02:00
Brian Fopiano e39f13bbb4 Merge pull request #1101 from VolmitDev/v1_20_R4
add datapack upgrade command
2024-05-19 08:49:15 -04:00
CrazyDev22 52ec80d384 add datapack upgrade command 2024-05-19 14:40:19 +02:00
CrazyDev22 d9f8909bdc fix crash when using iris as main world 2024-05-18 23:36:03 +02:00
CrazyDev22 65aa9dc343 minor headless performance improvements 2024-05-18 23:34:53 +02:00
CrazyDev22 9bf7fdf174 headless performance improvements 2024-05-17 20:05:37 +02:00
CrazyDev22 dafb59e5a8 improve chunk existing check 2024-05-17 16:45:59 +02:00
CrazyDev22 79f86ca87d woops wrong height 2024-05-17 16:39:57 +02:00
RePixelatedMC 1749fcf6b5 Merge pull request #18 from VolmitDev/mca
1.20.4 Headless
2024-05-17 16:04:43 +02:00
CrazyDev22 e1a0481cdf implement Headless for 1.20.4 2024-05-17 16:01:21 +02:00
RePixelatedMC 4576b95814 Fixed width offset with /iris std map when dragging 2024-05-16 17:32:32 +02:00
RePixelatedMC e113fa6a61 Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev 2024-05-16 16:55:51 +02:00
RePixelatedMC 07228afbff Noise benchmark reworkings 2024-05-16 16:55:14 +02:00
RePixelatedMC afdffc2453 Merge pull request #17 from VolmitDev/fix_jigsaw
Sync jigsaw
2024-05-16 13:41:35 +02:00
CrazyDev22 6348442962 add y lock to fix offset when placing objects 2024-05-16 13:17:17 +02:00
CrazyDev22 fd10c005b0 disable warp to prevent x/z offsets 2024-05-16 13:15:34 +02:00
CrazyDev22 44500d6af9 fix translation not rotating 2024-05-16 13:12:49 +02:00
CrazyDev22 6ddb0b5304 update gson library 2024-05-16 13:12:11 +02:00
Brian Fopiano 8ce0976f88 Merge pull request #1100 from VolmitDev/v1_20_R4
add 1.20.6 support
2024-05-15 19:52:08 -04:00
CrazyDev22 8345a58f2b change safeguard message 2024-05-15 18:11:05 +02:00
CrazyDev22 f92234297e add 1.20.6 support 2024-05-15 18:07:12 +02:00
RePixelatedMC 6a5bcd5990 Merge pull request #16 from VolmitDev/fix_jigsaw
sync Jigsaw
2024-05-15 17:40:29 +02:00
RePixelatedMC 1fe6d9a636 Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java
2024-05-15 09:29:48 +02:00
RePixelatedMC 97f6ab66c3 sync 2024-05-15 09:13:00 +02:00
RePixelatedMC b78c182d94 Map for studio 2024-05-13 16:30:18 +02:00
CrazyDev22 09a0f83013 add terminating pool overwrite 2024-05-11 12:51:29 +02:00
CrazyDev22 f68d7420e3 woops forgot description for SpreadType 2024-05-11 12:50:40 +02:00
CrazyDev22 55c0fa5f32 add command to sample the distances between structures 2024-05-10 15:58:57 +02:00
CrazyDev22 8b5d1d0298 rewrite jigsaw placement logic to prevent placements from being too close 2024-05-10 15:58:26 +02:00
CrazyDev22 b70e94dc65 make manual jigsaw place use the same calculations as normal 2024-05-10 15:43:01 +02:00
CrazyDev22 49a6552168 add forcePlace option to suppress placement checks 2024-05-10 15:41:42 +02:00
Brian Fopiano 0dd6892b28 Merge pull request #1097 from RePixelatedMC/master
Eco Items support
2024-05-09 15:44:13 -04:00
RePixelatedMC fec587edad Update README.md 2024-05-07 22:42:28 +02:00
RePixelatedMC 712937d661 Merge pull request #15 from VolmitDev/ecoitems
add Ecoitems support
2024-05-04 16:03:08 +02:00
CrazyDev22 ace434e7b0 add EcoItems support 2024-05-04 13:21:55 +02:00
RePixelatedMC 1e5aa6e8b0 Merge branch 'mca' of https://github.com/RePixelatedMC/Iris into PixelatedDev 2024-05-04 11:26:47 +02:00
RePixelatedMC 441b27f8f4 Merge branch 'VolmitSoftware:master' into mca 2024-05-04 11:26:23 +02:00
RePixelatedMC 3f3bf70212 Merge pull request #14 from VolmitDev/mca
Per World Dimension Types + better Datapack reload
2024-05-04 11:26:05 +02:00
RePixelatedMC a12eddbedf sync 2024-05-04 11:25:17 +02:00
CrazyDev22 12abc1709e add missing method to NMSBinding1X 2024-05-03 21:18:00 +02:00
CrazyDev22 9e6035e7b4 remove debug code 2024-05-03 21:17:02 +02:00
CrazyDev22 20b41d65d3 fixes to the per world dimensions + optimizations with the register method 2024-05-03 21:01:10 +02:00
CrazyDev22 1593bb2088 make smartVanillaHeight also adjust the logicalHeight 2024-05-03 19:57:34 +02:00
CrazyDev22 d4f9a20379 more removal of unnecessary settings 2024-05-03 19:56:43 +02:00
CrazyDev22 6e247597a4 fix loading the wrong IrisDimension on startup 2024-05-03 19:55:58 +02:00
CrazyDev22 8a8be4545c remove unnecessary minecraft dimension height calculations 2024-05-03 19:11:55 +02:00
CrazyDev22 05f4955989 implement proper datapack reload+per world height 2024-05-03 19:01:33 +02:00
CrazyDev22 93469fb3b4 include translate option into the bounding box of jigsaw pieces 2024-05-01 20:01:10 +02:00
RePixelatedMC a70258d69f Merge branch 'fix_jigsaw' of https://github.com/VolmitDev/Iris into PixelatedDev 2024-05-01 18:55:02 +02:00
CrazyDev22 c653d852e4 make jigsaw place command use the same method as normal gen 2024-05-01 18:46:22 +02:00
CrazyDev22 40163d25b8 add object shink command for fixing too large/small bounding boxes for objects 2024-05-01 18:44:45 +02:00
CrazyDev22 5b4265783e fix object w/h/d not changing with rotation 2024-05-01 18:42:23 +02:00
CrazyDev22 720417b6c8 fix shrinkwrap method 2024-05-01 18:39:42 +02:00
RePixelatedMC bb78f412e0 Sync / Progress on dump 2024-05-01 12:21:48 +02:00
RePixelatedMC a919b91efb Whohoo! we can now read mca files! 2024-04-30 11:14:35 +02:00
Brian Neumann-Fopiano a3dcf031c9 v+ , and the other v+ lol 2024-04-29 15:45:41 -04:00
RePixelatedMC 56eb4b6b84 Iris is small now! still a bit too big for spigot probably 2024-04-28 11:56:18 +02:00
RePixelatedMC 393cb362db - "Hey stop that! Its my World not yours."
- Safeguard displays itself before the worlds now.
- TODO: Minimize jar 7.5 MB > Under Spigots limit
2024-04-28 11:22:10 +02:00
RePixelatedMC 64e422036c Merge branch 'fix_jigsaw' of https://github.com/VolmitDev/Iris into PixelatedDev 2024-04-27 17:14:16 +02:00
RePixelatedMC 468e7ef018 Merge pull request #1094 from RePixelatedMC/master
Update README.md
2024-04-27 12:42:50 +02:00
RePixelatedMC e3e4ecbc5c Update README.md 2024-04-27 12:41:29 +02:00
RePixelatedMC 545ffc0e9d splash cleanup 2024-04-27 12:36:41 +02:00
RePixelatedMC 8125c8d5f4 fixes 2024-04-27 12:18:28 +02:00
RePixelatedMC 304b01d0cf Merge pull request #13 from VolmitDev/mca
add hotload
2024-04-27 10:54:38 +02:00
CrazyDev22 a5f687fd76 fix ServerConfigurator 2024-04-26 18:14:40 +02:00
CrazyDev22 fc1761a55b add nms method to hotload Datapacks 2024-04-26 18:14:22 +02:00
RePixelatedMC e7c9cad7f6 Merge pull request #1093 from RePixelatedMC/3.2.5
3.2.5
2024-04-26 18:06:37 +02:00
RePixelatedMC 979af82122 here crazy 2024-04-26 11:16:09 +02:00
RePixelatedMC 71a62b9c73 Merge pull request #12 from VolmitDev/mca
fix NMSBindings
2024-04-24 17:58:44 +02:00
CrazyDev22 bb020cab25 fix NMSBindings 2024-04-24 17:55:40 +02:00
RePixelatedMC 1ad35c1310 sync multiple mca fixes 2024-04-24 16:01:11 +02:00
CrazyDev22 6470b2f4a9 fix another null pointer 2024-04-23 13:49:30 +02:00
CrazyDev22 c1d5ba55cd fix null pointer 2024-04-23 13:49:10 +02:00
CrazyDev22 100e450514 fix jigsaw piece collision 2024-04-23 13:48:47 +02:00
RePixelatedMC 0f1d1d9860 Merge remote-tracking branch 'VolmitDev/fix_jigsaw' into PixelatedDev 2024-04-23 12:31:26 +02:00
RePixelatedMC 178a462a4e e 2024-04-23 12:29:19 +02:00
RePixelatedMC 8f4ae613f0 Merge branch '3.2.5' of https://github.com/RePixelatedMC/Iris into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/commands/CommandIris.java
2024-04-23 11:29:18 +02:00
RePixelatedMC 9e6963b6ce A lot of improvements thanks to CrazyDev! 2024-04-23 11:24:53 +02:00
CrazyDev22 cb9a73c60e rename IrisJigsawDistance to IrisJigsawMinDistance to avoid confusion 2024-04-23 10:28:04 +02:00
CrazyDev22 0e666a4c35 change rng noise for jigsaw component 2024-04-22 14:23:45 +02:00
CrazyDev22 c80138a354 Merge branch 'refs/heads/master' into fix_jigsaw 2024-04-22 14:14:48 +02:00
CrazyDev22 79a4ebcf65 woops 2024-04-19 15:29:28 +02:00
CrazyDev22 a7118aa785 fix jigsaw place command 2024-04-19 15:18:11 +02:00
RePixelatedMC 2555cd23a0 Merge branch 'VolmitSoftware:master' into 3.2.6 2024-04-19 15:17:21 +02:00
RePixelatedMC 484fbeca7b Merge branch '3.2.6' of https://github.com/RePixelatedMC/Iris into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/commands/CommandIris.java
#	core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java
2024-04-19 15:16:46 +02:00
RePixelatedMC 9a45e0df10 /iris support report ( gives server info )
and cleaned the main splash screen and fixed /iris import saying its loaded successfully even if it didnt
2024-04-19 15:15:08 +02:00
RePixelatedMC 9175296fc6 rev2.0 2024-04-19 12:43:57 +02:00
RePixelatedMC 5a4a86aeba - Iris Updater
Updates chunks thats it/
2024-04-19 10:14:16 +02:00
Brian Neumann-Fopiano 768fa7beb5 v+ 2024-04-19 02:47:02 -04:00
Julian Krings 6645eb9806 Merge branch 'VolmitSoftware:master' into fix_jigsaw 2024-04-18 16:04:30 +02:00
CrazyDev22 d7a283c99f fix underwater jigsaw placement 2024-04-18 16:00:47 +02:00
Brian Fopiano 8f019cd794 Merge pull request #1088 from RePixelatedMC/PixelatedDev
3.2.5 iris
2024-04-18 07:13:52 -04:00
CrazyDev22 c1d9cc62cb woops 2024-04-17 21:21:18 +02:00
Julian Krings 010a1e9e91 Merge branch 'VolmitSoftware:master' into fix_jigsaw 2024-04-17 21:16:08 +02:00
CrazyDev22 856c926cde add min distance to jigsaw structures 2024-04-17 21:15:15 +02:00
RePixelatedMC 72ed312654 wops 2024-04-16 18:09:03 +02:00
RePixelatedMC c7fe4723f7 Merge remote-tracking branch 'origin/master' into PixelatedDev 2024-04-16 17:18:51 +02:00
RePixelatedMC bfbea83a4a - Fixed NMS biome height.
- Improved /iris load
- Improved Vanilla height converter
- Removed WorldCreation unstable message
2024-04-16 17:18:02 +02:00
RePixelatedMC 55c58fe896 Merge pull request #1087 from RePixelatedMC/PixelatedDev
Should work
2024-04-16 16:34:12 +02:00
RePixelatedMC 72949e0950 Update IrisSettings.java 2024-04-16 16:32:52 +02:00
CrazyDev22 61c0ddb15b fix random jigsaw rotation and offset 2024-04-16 13:29:06 +02:00
Brian Neumann-Fopiano 9ac4024e4e v+ 2024-04-15 11:43:09 -04:00
RePixelatedMC 7fb2a51a33 sync 2024-04-12 17:08:13 +02:00
RePixelatedMC 5b7c0b2bc3 Merge branch 'PixelatedDev' of https://github.com/RePixelatedMC/Iris into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java
#	core/src/main/java/com/volmit/iris/engine/object/IrisBiomeGeneratorLink.java
2024-04-09 20:07:50 +02:00
RePixelatedMC fc15175ff9 Improved? 2024-04-09 20:06:32 +02:00
RePixelatedMC 1eec3a09c1 now it should work 2024-04-09 18:05:27 +02:00
RePixelatedMC 60d349dd8b test 2024-04-09 17:05:43 +02:00
RePixelatedMC 0c8d144ffb Merge remote-tracking branch 'VolmitDev/smartHeight' into PixelatedDev 2024-04-09 17:04:45 +02:00
RePixelatedMC b898f73a05 Fixed another potential mem leak
Added the base of smart320height
2024-04-09 17:04:32 +02:00
CrazyDev22 c2b9b0ba0e add smartVanillaHeight 2024-04-09 16:52:14 +02:00
RePixelatedMC 206a4e9057 Revert "sync laptop"
This reverts commit 63b8a935ae.
2024-04-06 17:07:14 +02:00
RePixelatedMC 83a0a7dd54 sync laptop 2024-04-06 17:06:06 +02:00
RePixelatedMC 63b8a935ae sync laptop 2024-04-05 18:43:30 +02:00
CrazyDev22 16affd11cc fix HMCLeaves leave decay 2024-04-04 23:46:14 +02:00
CrazyDev22 33b0dec9da fix ClassCastException when replacing to IrisBlockData 2024-04-04 23:45:25 +02:00
Brian Neumann-Fopiano 121a463788 v+
Woot
2024-04-03 03:03:32 -04:00
Julian Krings 3625e70948 Merge pull request #1085 from VolmitDev/impl_mmoitem
Impl MMOItems
2024-04-02 19:03:25 +02:00
CrazyDev22 3d90207172 make MMOItems getItemTypes synchronous to reduce lag 2024-04-02 12:03:00 +02:00
CrazyDev22 ca7dc19c81 make mmoitems item creation synchronous 2024-04-01 23:20:11 +02:00
CrazyDev22 d0f16dbbc4 fix using Type::toString instead of Type::getId for mmoitems 2024-04-01 23:18:41 +02:00
CrazyDev22 fd91c223b7 add unload debug tools 2024-04-01 22:08:28 +02:00
CrazyDev22 93690b766c implement MMOItemsDataProvider 2024-04-01 17:26:33 +02:00
CrazyDev22 cc4af9950b fix scanning for item provider instead of block provider to processUpdate 2024-04-01 17:26:10 +02:00
Brian Fopiano a6281483dd Merge pull request #1084 from RePixelatedMC/PixelatedDev
Pixelated dev
2024-03-31 12:45:06 -04:00
RePixelatedMC f587816b30 merges 2024-03-31 18:33:35 +02:00
RePixelatedMC eea3c3ab0b Merge remote-tracking branch 'origin/master' into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java
#	nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java
#	nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java
#	nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java
#	nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java
#	nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java
#	nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java
2024-03-31 18:25:26 +02:00
RePixelatedMC ba68ab9e06 f 2024-03-31 17:58:47 +02:00
RePixelatedMC eb94d97ea4 - Fixed caves breaksurface
- changes
2024-03-31 17:53:10 +02:00
Brian Neumann-Fopiano 04ad02719c d 2024-03-30 18:48:00 -04:00
Brian Fopiano f68f25b92f Merge pull request #1083 from VolmitDev/fix_object_collision_check
fix object collision check
2024-03-30 17:51:40 -04:00
Brian Fopiano 767cca36c8 Merge pull request #1080 from svdgoor/no-camel-1-19-R3
Cancels Camel spawns on 1-19-R3
2024-03-30 17:50:44 -04:00
Brian Fopiano b30172fe89 Merge pull request #1082 from VolmitDev/shut_up_spigot
Remove usage of System.out
2024-03-30 17:49:31 -04:00
CrazyDev22 b2934b0cc2 fix object collision check 2024-03-27 19:02:20 +01:00
RePixelatedMC 50686795d0 - Added /iris what region
- Sort of Fixed EngineStatus
- Added /iris hasAccess
- Added /iris irisworlds/worlds
- Added Some dev commands
- Redid / improved the mantle tectonic unload
- Convert still not working so ignore that
- Cleaned Iris of IrisPackBenchmarking.java im redoing it on second thought.
- Adjusted build.gradle
- Renamed some tectonic stuff
- Fixed Eta
- Fixed entities spawning inside blocks or at least it should.
- Lib update fixes all the iris titles/actionbars
- Cave spawning adjustments, now they get engine height if caveStartHeight is too large. <optional>
- Perhaps more?
2024-03-20 11:32:44 +01:00
CrazyDev22 5561a4dc2f fix wrong color bug 2024-03-20 08:46:56 +01:00
CrazyDev22 15b8780e17 remove usage of System.out 2024-03-20 08:44:00 +01:00
svdgoor 15c4d312cb Merge remote-tracking branch 'origin/master' 2024-03-18 17:29:02 +01:00
svdgoor ccbea89253 prevent spawning camels on 1.19.R3 2024-03-18 17:28:08 +01:00
RePixelatedMC 7b1e666b3b - No studio tectonic clearing. 2024-03-18 11:22:35 +01:00
RePixelatedMC 8af212fca8 - Fixed entities spawning inside blocks or at least it should. 2024-03-17 17:55:08 +01:00
Brian Fopiano 352ee7a622 Merge pull request #1079 from VolmitDev/better_plugin_compat
Better plugin compat
2024-03-17 02:55:19 -04:00
Brian Fopiano f26d0fac0b Merge pull request #1078 from VolmitDev/fix_v1_19_R3
Remove Cherry Grove from 1.19.4 biomes list
2024-03-17 02:55:03 -04:00
RePixelatedMC 9612fef2a4 - Added /iris what region
- Sort of Fixed EngineStatus
- Added /iris hasAccess
- Added /iris irisworlds/worlds
- Added Some dev commands
- Redid / improved the mantle tectonic unload
- Convert still not working so ignore that
- Cleaned Iris of IrisPackBenchmarking.java im redoing it on second thought.
- Adjusted build.gradle
- Renamed some tectonic stuff
- Perhaps more?
2024-03-16 12:40:40 +01:00
CrazyDev22 a691d49abc fix only the 0 0 chunk being updated 2024-03-08 19:35:07 +01:00
CrazyDev22 289eca35ec fix missing method parameter types 2024-03-07 19:41:28 +01:00
CrazyDev22 e9ca30257c woops 2024-03-07 18:48:09 +01:00
Julian Krings 6b21997baa Merge branch 'VolmitSoftware:master' into better_plugin_compat 2024-03-07 17:24:59 +01:00
CrazyDev22 6d3dbf84ef add HMCLeaves compatibility 2024-03-07 17:24:09 +01:00
CrazyDev22 eb45339c81 add oraxen furniture support 2024-03-07 17:23:32 +01:00
CrazyDev22 65e3fdd26c make implementing blocks that need world context possible 2024-03-07 17:23:00 +01:00
RePixelatedMC 34cad85942 - Fixed Eta 2024-03-01 12:32:53 +01:00
RePixelatedMC e407679d2b - Improved eta calculation 2024-03-01 11:44:00 +01:00
Julian Krings 8a08a3e148 Remove Cherry Grove from 1.19.4 biomes list 2024-02-27 11:30:40 +01:00
Brian Fopiano a01d27d273 Merge pull request #1075 from VolmitDev/fix_pregen
Fix Pregen oversight
2024-02-22 18:42:16 -05:00
Brian Fopiano 43f642ae8b Merge pull request #1076 from VolmitDev/custom-spawn-reason
Add implement custom spawn reason
2024-02-22 18:41:59 -05:00
CrazyDev22 93c1265de9 Add implement custom spawn reason 2024-02-22 21:25:37 +01:00
CrazyDev22 d08600fbaa Fix Pregen oversight 2024-02-22 17:27:38 +01:00
RePixelatedMC fb9f8d28f4 - Lib update fixes all the iris titles/actionbars
- Iris .schem Converter
2024-02-21 21:24:35 +01:00
Brian Fopiano cd3e9f772d Merge pull request #1074 from MoritzR200/master
Fixed file separators in '/iris import'
2024-02-17 09:54:59 -05:00
Brian Fopiano 7a95441799 Merge pull request #1071 from VolmitDev/fix_nullpointer
Fix nullpointer
2024-02-17 09:54:40 -05:00
Brian Fopiano 9345828c97 Merge pull request #1073 from RePixelatedMC/PixelatedDev
Fixes?
2024-02-17 09:54:14 -05:00
MoritzR200 58d88dd079 Fixed backslashes being used as file separators regardless of platform (in /iris import)). 2024-02-17 13:31:00 +01:00
RePixelatedMC 6cbc2374b3 whoopsies 2024-02-15 20:43:29 +01:00
RePixelatedMC 12bd11386d Merge remote-tracking branch 'CrazyDev/jigsaw_placement_fix' into PixelatedDev 2024-02-14 09:13:37 +01:00
RePixelatedMC 0497045388 - Fixed /Iris height when ran in console
- Added Dynamic worldHeight
2024-02-13 08:51:48 +01:00
Julian Krings d3f1640855 Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2024-02-12 21:30:19 +01:00
CrazyDev22 960e2fcc61 try again if engine setup fails 2024-02-12 11:43:21 +01:00
CrazyDev22 6a012cf400 fix setup being set before engine setup 2024-02-12 11:42:47 +01:00
Brian Neumann-Fopiano d658ec2099 oops 2024-02-04 17:37:00 -05:00
Brian Fopiano c9a07bd9d7 Merge pull request #1069 from RePixelatedMC/PixelatedDev
This should be the last pr for the update
2024-01-26 07:35:39 -05:00
RePixelatedMC c0136585e6 Merge remote-tracking branch 'origin/master' 2024-01-26 10:59:14 +01:00
RePixelatedMC dc4cfc49ad Merge remote-tracking branch 'origin/master' into PixelatedDev 2024-01-26 10:58:48 +01:00
RePixelatedMC 9fc72868d7 disabled perms check 2024-01-26 10:57:03 +01:00
Brian Fopiano 7d4c9d6c1e Merge pull request #1066 from VolmitDev/better-version-compat
Better version compat
2024-01-25 10:51:56 -05:00
Brian Fopiano e68b1a52ee Merge pull request #1067 from RePixelatedMC/PixelatedDev
Feature and fIxes!
2024-01-25 10:51:45 -05:00
RePixelatedMC 955e8622b5 Merge remote-tracking branch 'VolmitDev/better-version-compat' into PixelatedDev 2024-01-25 14:57:47 +01:00
Julian Krings 82640de06b v+ 2024-01-25 14:47:19 +01:00
RePixelatedMC c93cb19563 - !Fixed pregen skipping chunks
- !Improved stacking for partof
- improved DeepSearch
- Some SFG Fixes
- Added some Todos
- Improvements to objects being placed in the air
- Studio BoardSVC for debug
2024-01-25 11:23:38 +01:00
Julian Krings ccb7855d7d Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2024-01-20 15:10:05 +01:00
RePixelatedMC bad547356c Disabled for now 2024-01-14 16:43:39 +01:00
RePixelatedMC 1740bb0021 whoop 2024-01-14 16:35:00 +01:00
RePixelatedMC 20f05bf317 whoop 2024-01-14 16:32:15 +01:00
RePixelatedMC 669ede5482 remove turbo for now 2024-01-14 10:18:41 +01:00
Julian Krings ab658f58f9 Merge branch 'VolmitSoftware:master' into better-version-compat 2024-01-13 10:52:50 +01:00
Brian Fopiano 1b3916749a Merge pull request #1063 from RePixelatedMC/PixelatedDev
Quite some more fixes
2024-01-12 10:38:06 -05:00
RePixelatedMC e29ec657d3 e 2024-01-12 16:10:02 +01:00
RePixelatedMC 04f3a226a1 whoop 2024-01-12 16:07:02 +01:00
RePixelatedMC 099d1cc362 - Added DeepSearchPregenerator.java < although not finished the base is functional >
- Fixes!
- Dynmap SFG detection now works!
2024-01-12 15:56:04 +01:00
RePixelatedMC c11e9bf704 Merge remote-tracking branch 'origin/master' into PixelatedDev 2024-01-11 22:56:48 +01:00
RePixelatedMC ffa0b984a0 whoop 2024-01-10 18:47:14 +01:00
RePixelatedMC f0f9261ed3 whoop 2024-01-04 15:29:23 +01:00
RePixelatedMC ba433bc244 - Display true height instead of engine height option added! 2024-01-04 15:26:25 +01:00
Julian Krings 27f8ad982a Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2023-12-31 19:15:39 +01:00
CrazyDev22 9b89ae7846 reimplement IrisCompat 2023-12-31 19:13:57 +01:00
Brian Fopiano c11b5342e3 Merge pull request #1062 from RePixelatedMC/master
Fixed a rare crash - Crazydev
2023-12-30 08:56:53 -05:00
RePixelatedMC b9c88f5b12 Fixed a rare crash - Crazydev 2023-12-30 14:36:07 +01:00
RePixelatedMC 02c7ab71d4 Merge remote-tracking branch 'PixelsV3/PixelatedDev' into PixelatedDev
# Conflicts:
#	core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java
2023-12-30 14:31:22 +01:00
RePixelatedMC 8ec1ac9875 sync 2023-12-30 14:29:31 +01:00
RePixelatedMC e03027a386 Now using klist for tounload 2023-12-30 14:15:59 +01:00
Julian Krings 387cf566c9 Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2023-12-30 11:25:33 +01:00
Brian Fopiano dff7665934 Merge pull request #1053 from RePixelatedMC/Pixeldev
Pixeldev
2023-12-29 16:32:21 -05:00
RePixelatedMC 9421d788f3 Backup 2023-12-28 17:56:30 +01:00
RePixelatedMC d960574b24 Ah yes 2023-12-28 14:41:16 +01:00
RePixelatedMC e8671cbbf0 whoop 2023-12-27 09:13:14 +01:00
RePixelatedMC cbf8039eea Cleanup p2 2023-12-27 09:09:11 +01:00
RePixelatedMC d7415e3711 Merged CommandWorldManager.java into CommandIris.java 2023-12-27 09:05:05 +01:00
RePixelatedMC 26e71ee6b5 small tweak 2023-12-27 08:06:40 +01:00
RePixelatedMC f8c42b3e48 cleaning up 2023-12-27 07:35:24 +01:00
RePixelatedMC 886bed9634 o 2023-12-26 13:38:31 +01:00
RePixelatedMC 7b7023fc99 Added ToUpgradedVersion 2023-12-26 13:32:37 +01:00
RePixelatedMC 701bfe4a54 Merge pull request #10 from CrazyDev05/Pixeldev
Fix lastUse and Implement MultiBurst
2023-12-25 14:14:16 +01:00
CrazyDev22 290d7a93ab switch to burst 2023-12-25 13:53:45 +01:00
CrazyDev22 729bcc7ba0 fix lastUse 2023-12-25 13:52:31 +01:00
RePixelatedMC e46b68e2f3 comment 2023-12-24 16:41:20 +01:00
RePixelatedMC cdf73c4629 Nvmr 2023-12-24 16:40:44 +01:00
RePixelatedMC 2f0dd2d172 Quick fix 2023-12-24 16:17:41 +01:00
RePixelatedMC 7e5704bf80 No idea why crazy recommended this 2023-12-24 15:17:56 +01:00
RePixelatedMC ef96fb9a0e Removed debug code 2023-12-24 15:06:53 +01:00
RePixelatedMC f2370ad814 Merge pull request #9 from CrazyDev05/Pixeldev
Sync with Master
2023-12-24 15:03:24 +01:00
RePixelatedMC 0783620104 Way to tired 2023-12-24 15:00:02 +01:00
RePixelatedMC 2193b02c0a Removed debug code 2023-12-24 14:40:41 +01:00
RePixelatedMC aef05bad20 Deleted leftovers and a couple of changes 2023-12-24 14:32:13 +01:00
CrazyDev22 e002ab6378 disable 1.20.4 nms bindings for now 2023-12-23 21:17:35 +01:00
Julian Krings a2c2b4a2d4 Sync with Master 2023-12-23 20:53:33 +01:00
Julian Krings 3ae896457b Merge branch 'Pixeldev' into origin 2023-12-23 20:53:18 +01:00
Julian Krings 683a90ed09 Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2023-12-23 20:52:13 +01:00
Brian Fopiano 5685e25234 Merge pull request #1061 from CrazyDev05/build_fix
fix missing dependency
2023-12-23 14:43:07 -05:00
CrazyDev22 6edf083e7b fix <world>/iris/iris/pack path 2023-12-23 20:39:54 +01:00
CrazyDev22 995d1495eb fix missing dependency 2023-12-23 20:14:43 +01:00
Julian Krings d5f74631cd Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2023-12-23 18:37:59 +01:00
Brian Fopiano dd5b9b2e81 Merge pull request #1060 from CrazyDev05/build_fix
Build fix
2023-12-23 12:32:09 -05:00
RePixelatedMC ca78200c1c Deleted leftovers 2023-12-23 15:01:27 +01:00
RePixelatedMC d73c2a65ae - Saving iris and mc version data to engine 2023-12-23 14:57:33 +01:00
CrazyDev22 0ee2cb8e3c why did I add that 2023-12-23 11:47:26 +01:00
CrazyDev22 5ba429ec73 implement getBiomes method 2023-12-23 11:40:06 +01:00
CrazyDev22 709825d073 decrease build time to ~30s
revert single version building
2023-12-23 11:39:40 +01:00
Julian Krings 3fe73af5e8 Merge branch 'VolmitSoftware:master' into jigsaw_placement_fix 2023-12-23 10:20:30 +01:00
Brian Fopiano ccc0b0dd7d Merge pull request #1057 from CrazyDev05/single_nms
add ability to only compile for one mc version
2023-12-22 20:03:58 -05:00
Brian Fopiano 1114ede267 Merge pull request #1058 from IkyMax/master
Update to 1.20.4
2023-12-22 20:03:37 -05:00
Brian Fopiano 5546f12499 Merge pull request #1052 from CrazyDev05/worldedit_fix
Worldedit fix
2023-12-22 20:03:24 -05:00
Brian Fopiano 4f38acb592 Merge pull request #1055 from CrazyDev05/multiversion_fix
NoSuchFieldError: CHERRY_GROVE fix
2023-12-22 20:03:11 -05:00
RePixelatedMC 39f59e023c Merge pull request #7 from CrazyDev05/Pixeldev
implement showing location instead of teleporting
2023-12-22 21:36:42 +01:00
CrazyDev22 5ed5322fce implement showing location instead of teleporting 2023-12-22 21:23:01 +01:00
RePixelatedMC 33b926d547 Merge pull request #6 from CrazyDev05/Pixeldev
Fixes!
2023-12-22 19:54:54 +01:00
CrazyDev22 af8778c6cd fix ArrayIndexOutOfBoundsException
fix NullPointerException
fix ConcurrentModificationException x2
2023-12-22 19:36:07 +01:00
RePixelatedMC bb4ad08cc5 Merge pull request #5 from CrazyDev05/Pixeldev
More Improvements
2023-12-22 16:39:30 +01:00
CrazyDev22 ee4eb7b3f0 fix 2023-12-22 16:34:39 +01:00
CrazyDev22 5bc024a6ee Merge remote-tracking branch 'pixeldev/Pixeldev' into Pixeldev 2023-12-22 16:33:51 +01:00
RePixelatedMC 0546caeb05 - Fixed Process Threads displaying the incorrect value on startup. 2023-12-22 16:24:48 +01:00
CrazyDev22 a75348d3ee Merge remote-tracking branch 'pixeldev/Pixeldev' into Pixeldev 2023-12-22 16:19:49 +01:00
CrazyDev22 3f3f947f43 change the Param description 2023-12-22 16:17:56 +01:00
RePixelatedMC 5127b7c959 - Added DiskSpace to Splash
- Changed form ofSize to contain a space
2023-12-22 16:16:48 +01:00
CrazyDev22 a28df78792 switch to lz4 block compression 2023-12-22 16:13:57 +01:00
CrazyDev22 4f888f16bc Add Compression Test Command 2023-12-22 16:13:46 +01:00
RePixelatedMC 55ed9d7132 - Added DiskSpace check 2023-12-22 15:59:05 +01:00
CrazyDev22 779a56c3a1 add compression test command 2023-12-22 15:50:27 +01:00
RePixelatedMC f6440bde07 - Added Sufficient Permission check 2023-12-22 15:33:22 +01:00
RePixelatedMC 930dc2fd1c Merge pull request #4 from CrazyDev05/Pixeldev
increase unload speed
2023-12-22 11:48:23 +01:00
CrazyDev22 1e96fd529b increase unload speed 2023-12-22 11:17:55 +01:00
RePixelatedMC 167c865bb2 e 2023-12-22 09:00:57 +01:00
RePixelatedMC 4e6b6c11a3 Merge remote-tracking branch 'PixelsV3/Pixeldev' into Pixeldev
# Conflicts:
#	core/src/main/java/com/volmit/iris/util/mantle/Mantle.java
2023-12-22 08:37:27 +01:00
RePixelatedMC bfdb71ca91 Merge pull request #3 from CrazyDev05/Pixeldev
TectonicPlate unloading improvements
2023-12-22 08:35:35 +01:00
IkyMax f590f9824e Update to 1.20.4 2023-12-21 15:58:45 -06:00
CrazyDev22 b3a087702d show unloaded tectonicplates count 2023-12-21 22:36:50 +01:00
CrazyDev22 1a786fb418 give hyperLock a purpose again 2023-12-21 22:36:05 +01:00
CrazyDev22 650d38e212 Implement MultiThreading in the TectonicPlates unloader 2023-12-21 22:30:08 +01:00
RePixelatedMC 130cb7ad93 So that works 2023-12-21 22:04:19 +01:00
CrazyDev22 85e8ffeaa3 Merge remote-tracking branch 'pixeldev/Pixeldev' into Pixeldev 2023-12-21 20:55:37 +01:00
RePixelatedMC db40f608fa try this out 2023-12-21 20:55:10 +01:00
RePixelatedMC a7b471682a try this out 2023-12-21 15:39:26 +01:00
CrazyDev22 4595fea7d7 fix NullPointerException 2023-12-21 12:34:14 +01:00
CrazyDev22 4580f8d9a4 fix StackOverflowError 2023-12-21 12:21:20 +01:00
CrazyDev22 37f76fa133 Dynamically define tectonicLimit and cooldown 2023-12-20 14:43:37 +01:00
RePixelatedMC 5507c35547 new structure 2023-12-17 17:42:28 +01:00
CrazyDev22 350328f99c fix remapping not working 2023-12-16 18:40:53 +01:00
CrazyDev22 91ec6b773d add ability to only compile for one mc version 2023-12-16 17:22:45 +01:00
RePixelatedMC c0fdf23133 cleanup 2023-12-16 14:32:06 +01:00
RePixelatedMC 28523a9593 e 2023-12-16 14:29:56 +01:00
RePixelatedMC 07e0b52ea3 e 2023-12-16 14:08:10 +01:00
RePixelatedMC 008e758da8 To many issues that are just not worth dealing with 2023-12-16 13:51:31 +01:00
RePixelatedMC 7ce219a6a8 - Added warning mode
- ignoreUnstable Changed to ignoreBootMode
2023-12-16 13:45:21 +01:00
RePixelatedMC 1809d4a53f - Added JDK version check for an SFG
- Fixed duplicate cpu message on boot when on intel and unstable
2023-12-15 20:14:34 +01:00
RePixelatedMC 54ae026c2b - Fixed LazyPregenerator.java and improved it. ( Speed decrease on server restart when lazy starts the job up dont know why)
- Mem leak fix only works when there is 1 iris world for now ( working on it ) + A capped performance limit still very alpha like seems stable tho.

- Disabled HotDropWorldSVC acts a bit weird sometimes.
2023-12-15 19:06:53 +01:00
RePixelatedMC 57c647fa0e Whoops 2023-12-07 18:09:36 +01:00
RePixelatedMC 9745f23fb2 - Saving the LazyPregen Progress in world folder
- Added option to pause to LazyPregen
- Added option to stop to LazyPregen
- Re-Added the functionality continue pregen on startup
2023-12-07 17:57:45 +01:00
RePixelatedMC 7d2062f298 e 2023-12-07 16:06:33 +01:00
RePixelatedMC 306a1948cf - Moved iris regen to studio
- Moved Iris LazyPregen into its own cmd
- Fixed the mem leak "i hope"
- Added Silent option to the lazy pregen
2023-12-07 16:04:40 +01:00
RePixelatedMC ac4c00d3f2 Thanks Crazydev! 2023-12-07 14:27:08 +01:00
RePixelatedMC db43bc300e Sync 2023-12-07 11:26:14 +01:00
RePixelatedMC 6494cded62 Sync 2023-12-07 10:41:17 +01:00
RePixelatedMC b5c1e7e2da Sync 2023-12-06 15:56:19 +01:00
RePixelatedMC 83e311870f Sync 2023-12-03 16:56:56 +01:00
RePixelatedMC 8fc6e3b335 Dev code + added maxcpm for lazy gen 2023-12-03 12:45:36 +01:00
RePixelatedMC 18a69e97b3 not really lazy but oh well at least it fixed a crash 2023-11-30 10:19:57 +01:00
RePixelatedMC 80d8449f02 Bug when on intel cpu and unstable mode and changed how SFG works 2023-11-29 18:04:56 +01:00
RePixelatedMC d830aa1d1a Bug when on intel cpu and unstable mode and moved more to SFG 2023-11-29 16:26:34 +01:00
RePixelatedMC d473126d4c What was this even meant to do? 2023-11-29 16:16:11 +01:00
RePixelatedMC 17f8fe69fb so I didnt know that 2023-11-29 14:43:06 +01:00
Julian Krings 7263d102c7 cleanup / possibly fix a bug 2023-11-29 14:34:00 +01:00
RePixelatedMC 815b2235a2 FINALLY 2023-11-29 13:48:58 +01:00
RePixelatedMC f61247f8bb Raw data 2023-11-29 11:31:26 +01:00
RePixelatedMC 205d9d528e back to debug 2023-11-29 10:47:45 +01:00
RePixelatedMC 1044f901bd o 2023-11-29 10:41:48 +01:00
RePixelatedMC de261a38fd o 2023-11-29 10:39:48 +01:00
RePixelatedMC 81776e60fb So now it actually it works 2023-11-29 10:31:44 +01:00
RePixelatedMC 0c179eae86 e 2023-11-26 19:42:25 +01:00
RePixelatedMC c30de98449 e 2023-11-26 19:02:50 +01:00
RePixelatedMC 47990cde46 Thanks @CrazyDev! 2023-11-24 20:44:56 +01:00
Julian Krings b098c210ce removed unnecessary code 2023-11-24 17:03:32 +01:00
RePixelatedMC 6edd8bf119 Ah 2023-11-24 15:55:17 +01:00
RePixelatedMC de0e124ebb For fuck's sake, why haven't I done this earlier 2023-11-24 15:38:12 +01:00
RePixelatedMC 5fc012a557 no way im spending more time on something stupid as this 2023-11-24 15:27:05 +01:00
RePixelatedMC 32afcc3478 - Fixed hotdrop being activated on world creation
- Improved /iris delete
- Unfinished code
2023-11-24 15:21:15 +01:00
RePixelatedMC a27f37945d Merge branch 'VolmitSoftware:master' into Pixeldev 2023-11-24 14:26:06 +01:00
RePixelatedMC 0adfe6911f smth like this 2023-11-24 13:08:42 +01:00
RePixelatedMC 8bb1964b30 smth like this 2023-11-23 16:49:45 +01:00
RePixelatedMC cc95e1ae63 More more 2023-11-23 16:47:55 +01:00
Julian Krings 9843f18c96 Fix jigsaw piece collision/merging 2023-11-22 13:45:55 +01:00
Julian Krings 18ea61fcc0 Move Biome List for DummyBiomeProvider into NMSBindings 2023-11-21 12:50:54 +01:00
Julian Krings 5b3fdb6e62 Optimise WorldEdit check 2023-11-17 15:43:59 +01:00
RePixelatedMC c5220c8d06 Fix for HotDropWorldSVC.java not working 2023-11-17 12:20:47 +01:00
RePixelatedMC 74e87a7fae Fix for HotDropWorldSVC.java not working 2023-11-17 12:17:46 +01:00
Julian Krings c809f50c3b Optimise check for wand item 2023-11-17 11:49:23 +01:00
Julian Krings e7e46f78c7 Disable WorldEditLink when WorldEdit not present/loaded 2023-11-17 11:41:57 +01:00
Julian Krings 2b449a1f3d Add Settings check before calling WorldEditLink 2023-11-17 11:40:56 +01:00
Brian Neumann-Fopiano f257f2c551 updated my mac path 2023-11-15 15:47:40 -05:00
Brian Fopiano 7f91d73d27 Merge pull request #1051 from RePixelatedMC/Pixeldev
Whoops
2023-11-15 12:24:00 -05:00
RePixelatedMC 15a90edb03 More robust and some debugging 2023-11-15 18:06:54 +01:00
RePixelatedMC b2f1e16d9a more aggressive 2023-11-15 16:56:34 +01:00
RePixelatedMC f544455708 Merge branch 'VolmitSoftware:master' into Pixeldev 2023-11-15 16:38:43 +01:00
RePixelatedMC 5118997188 Whoop 2023-11-15 16:37:34 +01:00
Brian Fopiano b6ae649cd1 Merge pull request #1050 from RePixelatedMC/Pixeldev
Mem leak Fix + more
2023-11-15 10:02:52 -05:00
RePixelatedMC a6d0d85b84 deleted unused class 2023-11-15 12:48:26 +01:00
RePixelatedMC c71c2494af sync 2023-11-15 12:22:36 +01:00
RePixelatedMC 4a3a7bc98d Whoop 2023-11-15 11:16:33 +01:00
RePixelatedMC 4e4f7f693d Sync 2023-11-15 11:14:26 +01:00
RePixelatedMC 7a729c39a6 e 2023-11-15 09:58:46 +01:00
RePixelatedMC 48b757f693 Forgot those 2023-11-15 08:43:38 +01:00
RePixelatedMC b449938730 Merge branch 'VolmitSoftware:master' into Pixeldev 2023-11-15 08:18:39 +01:00
RePixelatedMC ba55ff6a4b typo 2023-11-12 10:19:39 +01:00
RePixelatedMC 2b4bc621ac misc 2023-11-11 21:56:28 +01:00
RePixelatedMC acac2d0fc5 Removed GPT 4 Comments lmao 2023-11-11 21:26:31 +01:00
RePixelatedMC 6ff5975918 - HotDrop iris worlds lmao 2023-11-11 21:25:24 +01:00
RePixelatedMC 8912cfe37b fixed declaration 2023-11-11 16:14:37 +01:00
RePixelatedMC 9e02c318a1 Refactoring and a new pregen message 2023-11-11 09:50:24 +01:00
Brian Fopiano f6b51c21d5 Merge pull request #1049 from RePixelatedMC/Pixeldev
[ Ready ] SAFEGUARD
2023-11-06 17:21:55 -05:00
RePixelatedMC df3e4b055f Merge branch 'master' into Pixeldev 2023-11-06 17:30:18 +01:00
RePixelatedMC ffcd2b4179 z 2023-11-06 15:22:23 +01:00
RePixelatedMC 7afdbc2a53 - Added Lazy pregen as an option 2023-11-06 15:03:40 +01:00
RePixelatedMC 5004481685 - Fixed /iris remove for real this time lol
- Changed it back to /iris instead of /iris worldmanager for easier access
2023-11-04 20:04:05 +01:00
RePixelatedMC 1905741a7c Matched the CPU Brand color theme lol 2023-11-04 17:30:22 +01:00
RePixelatedMC 8bf7451107 - Removed debug code
- fixed unstableprompt
2023-11-04 17:00:09 +01:00
RePixelatedMC fdfb708ce0 Removed FixUnstable CMD for now.. 2023-11-04 16:43:10 +01:00
Brian Fopiano 68a4eee8bb Merge pull request #1047 from CrazyDev05/executable_items
add ExecutableItems Implementation
2023-11-04 11:17:14 -04:00
Brian Fopiano 746ebfffbd Merge pull request #1044 from CrazyDev05/object_fix
Object Spawning pattern fix
2023-11-04 11:16:44 -04:00
Brian Fopiano 213f8645bc Merge pull request #1045 from CrazyDev05/papi_fix
Fix PapiExpansion not persisting after papi reload
2023-11-04 11:16:14 -04:00
Brian Fopiano 1b2541bd84 Merge pull request #1043 from CrazyDev05/build_fix
build.gradle fix
2023-11-04 11:15:57 -04:00
RePixelatedMC 522de89792 Fixed Crash on lower end servers + Cleanup 2023-11-04 14:35:40 +01:00
RePixelatedMC 5feaa45edf cleanup 2023-11-03 10:20:59 +01:00
RePixelatedMC 9333775e0e wait no 2023-11-03 09:23:28 +01:00
RePixelatedMC afd15ad450 A lot of cleanup 2023-11-03 09:21:59 +01:00
RePixelatedMC e7fc9e6fe5 Test 2023-11-03 08:28:19 +01:00
RePixelatedMC 021f9b1d0e Cleanup / Fixes 2023-11-02 16:55:47 +01:00
RePixelatedMC dc21f05482 Cleanup 2023-11-02 13:42:50 +01:00
RePixelatedMC c22ccf71a7 Added setting option for ChunkHandler.java and no more random freezes. 2023-11-02 13:03:09 +01:00
RePixelatedMC d5b9a074e6 Additions to ChunkHandler.java 2023-11-02 12:53:30 +01:00
RePixelatedMC 63e6104736 EXPERIMENTAL ChunkHandler.java 2023-11-02 12:24:13 +01:00
RePixelatedMC de475c9561 Changes 2023-11-01 16:02:53 +01:00
RePixelatedMC 3f4fa0c67c Disabled Pack Benchmark for now.. 2023-11-01 15:29:17 +01:00
RePixelatedMC ca3f228b89 Cleaning and idi- i mean user issue prevention system. ALPHA 2023-10-29 20:53:56 +01:00
RePixelatedMC 2b159041ac Cleaned SFG a bit 2023-10-29 14:14:37 +01:00
RePixelatedMC 5fa564969e Added cmd to see what is causing the instability 2023-10-29 14:09:25 +01:00
RePixelatedMC 8ba8627281 Improved pack Benchmarking not done yet.. 2023-10-28 20:51:32 +02:00
RePixelatedMC a12cb59a51 Changed how ServerBootSFG Works Part1.
Added More warnings and Checks.
Improved Unstable Colored text.
Text for console and Player errors when in unstable.
2023-10-28 20:49:47 +02:00
RePixelatedMC dd124fab33 Changed to RED Iris tag when its unstable. 2023-10-28 19:33:16 +02:00
BuildTools 9f8ef4c1f3 add ExecutableItems Implementation 2023-10-27 21:25:46 +02:00
BuildTools 563c6d0403 make object collision configurable 2023-10-26 20:22:34 +02:00
RePixelatedMC 1bad0fe048 test 2023-10-26 19:46:38 +02:00
RePixelatedMC b40dcd3164 fixup! Pack Benchmarking - Iris pregen is broken what it relies on so it doesnt work. Issue: Generated and total chunks dont end up as the same value after the pregen is done 2023-10-26 17:52:33 +02:00
RePixelatedMC 8a0b443d28 Pack Benchmarking
- Iris pregen is broken what it relies on so it doesnt work.
Issue: Generated and total chunks dont end up as the same value after the pregen is done
2023-10-26 17:28:43 +02:00
Julian Krings 5f38503ac5 Fix PapiExpansion not persisting after papi reload 2023-10-24 17:43:36 +02:00
BuildTools 231d1209c9 make object spawning more random 2023-10-24 16:07:43 +02:00
RePixelatedMC 06bc180127 renames + dev sync 2023-10-24 15:16:39 +02:00
BuildTools fb2221fa8d fix iris task having the wrong source path 2023-10-23 16:11:40 +02:00
BuildTools e1c7f38bb8 re-add setup task 2023-10-23 15:46:21 +02:00
Brian Neumann-Fopiano a82fa578eb nana 2023-10-23 05:51:54 -04:00
Brian Fopiano 0ecefdcc1c Merge pull request #1041 from CrazyDev05/multi_version
Make Iris multi version compatible
2023-10-23 05:17:34 -04:00
CrazyDev22 8bacc8128b move base project to 'core'
add build script for nms
add build script for core
add build script for project
move existing nms references to nms/v1_20_R1
add 1.19 and 1.20.2 compatibility
2023-10-23 08:54:30 +02:00
Brian Neumann-Fopiano 3dff96152c v+ 2023-10-22 23:35:58 -04:00
Brian Fopiano 79e198d441 Merge pull request #1038 from RePixelatedMC/repixelatedmc
Repixelatedmc
2023-10-22 23:17:53 -04:00
Brian Fopiano e6cf33e262 Merge pull request #1040 from CrazyDev05/repo_fix
Fix build.gradle

Thanks
2023-10-22 23:17:26 -04:00
RePixelatedMC f839ca674b Remove / Delete Iris world actually works now lol 2023-10-22 20:08:58 +02:00
RePixelatedMC f9427d1258 ROTATION FIX WHOHOO 2023-10-22 15:22:01 +02:00
RePixelatedMC bd2cdc9bd0 idk some fix 2023-10-22 12:09:10 +02:00
RePixelatedMC 67352311de WHooPs 2023-10-22 11:02:46 +02:00
RePixelatedMC 555a7682e1 fixes 2023-10-22 11:01:07 +02:00
RePixelatedMC 19d94489d7 Merge pull request #2 from RePixelatedMC/repixelated-DEV
Repixelated dev
2023-10-22 10:58:00 +02:00
RePixelatedMC 0175cfa986 fixes 2023-10-22 10:54:12 +02:00
RePixelatedMC 1b0ff36d51 cleanup 2023-10-22 10:44:32 +02:00
RePixelatedMC f5c499ab60 Fixes + cleanup 2023-10-22 10:40:40 +02:00
RePixelatedMC 81e3fd346a Rewrote The update process and made it more manual 2023-10-22 10:13:18 +02:00
RePixelatedMC 3bca1f5304 Fixes? 2023-10-22 09:20:13 +02:00
RePixelatedMC 593e96a31a added WindowsCPUSpeedTest 2023-10-22 08:47:33 +02:00
Julian Krings 169b783e4f Fix build.gradle 2023-10-21 22:47:01 +02:00
RePixelatedMC f129742a41 cleanup 2023-10-21 20:19:03 +02:00
RePixelatedMC 01016d1b14 modes? 2023-10-21 20:18:02 +02:00
RePixelatedMC a883e43acb eh 2023-10-21 15:26:24 +02:00
RePixelatedMC e0d0657e7b thx coco 2023-10-21 15:25:55 +02:00
RePixelatedMC e4c27bd49c thx coco 2023-10-08 14:10:44 +02:00
RePixelatedMC 7a31fd31eb whoop 2023-10-08 14:01:28 +02:00
RePixelatedMC 4f0f2c3213 grammar fix 2 2023-10-08 14:00:57 +02:00
RePixelatedMC d9889b06ad grammar fix 2023-10-08 13:59:56 +02:00
RePixelatedMC 9f5cff4ab8 brr 2023-10-06 12:41:21 +02:00
RePixelatedMC 958543a171 brr 2023-10-06 12:31:34 +02:00
RePixelatedMC b8e03f5e13 Merge remote-tracking branch 'origin/master' 2023-10-06 12:28:40 +02:00
Brian Neumann-Fopiano e76218b39f forgot the overworld , this is the V+ 2023-10-06 12:13:36 +02:00
Brian Neumann-Fopiano 7d1a375bda v+ 2023-10-06 12:13:36 +02:00
CrazyDev22 dfaa80f1e4 Add Color Gradient 2023-10-06 12:13:36 +02:00
CrazyDev22 8939a903be Add NullablePlayerHandler
Add Teleport Command
2023-10-06 12:13:32 +02:00
CrazyDev22 d6f1ea78fa Fix NullPointerException 2023-10-06 12:13:14 +02:00
CrazyDev22 f25c4c74be Fix IrisJigsawPiece tectonic plate panic 2023-10-06 12:13:14 +02:00
BuildTools 1619df1ba3 remove debug info 2023-10-06 12:13:14 +02:00
BuildTools 566169cdbf better fix for jigsaw loot 2023-10-06 12:13:13 +02:00
RePixelatedMC ee3e631789 comment 2023-10-06 11:47:24 +02:00
RePixelatedMC 8202754988 remove world to worldmanager 2023-10-06 10:30:24 +02:00
RePixelatedMC 447d7596dc added evacuate 2023-10-06 10:26:43 +02:00
RePixelatedMC 7c4568066f IrisSafeguard Prototype idea
WorldManager ( Not done WIP )
2023-10-06 10:14:33 +02:00
RePixelatedMC 46ee7a5983 IrisSafeguard Prototype idea 2023-10-04 14:12:53 +02:00
Brian Neumann-Fopiano 5a8ef99afa forgot the overworld , this is the V+ 2023-10-04 03:30:55 -04:00
Brian Neumann-Fopiano 571729cfe4 v+ 2023-10-04 03:24:32 -04:00
Brian Fopiano c1de316ed6 Merge pull request #1036 from CrazyDev05/teleport
Add Teleport Command
2023-10-04 03:22:29 -04:00
Brian Fopiano 1e316acd64 Merge pull request #1037 from CrazyDev05/jigsaw_piece_mantle_panic_fix
Fix IrisJigsawPiece tectonic plate panic
2023-10-04 03:21:43 -04:00
CrazyDev22 f89460538d Fix NullPointerException 2023-10-03 14:20:40 +02:00
CrazyDev22 0bf0062c2c Fix IrisJigsawPiece tectonic plate panic 2023-10-01 22:28:03 +02:00
CrazyDev22 540d1b5801 Add Color Gradient 2023-10-01 17:45:40 +02:00
CrazyDev22 8f0d1b7b7b Add NullablePlayerHandler
Add Teleport Command
2023-10-01 16:50:50 +02:00
Brian Fopiano 5061791dcf Merge pull request #1034 from RePixelatedMC/master
Fixed - /iris object undo / Added - IrisLootEvent
2023-09-27 08:15:06 -04:00
Brian Fopiano ce98264552 Merge pull request #1033 from CrazyDev05/jigsaw_loot_fix_2
Better Jigsaw loot fix
2023-09-27 08:14:51 -04:00
RePixelatedMC 15e6750e11 Merge pull request #1
IrisLootEvent
2023-09-27 10:31:43 +02:00
RePixelatedMC 7a20421580 Added IrisLootEvent.java and some debugging to that 2023-09-27 10:15:11 +02:00
RePixelatedMC f42f06226c Optimized it thx coco 2023-09-25 15:12:45 +02:00
RePixelatedMC cce3d74c52 Merge remote-tracking branch 'origin/master' 2023-09-25 15:03:41 +02:00
RePixelatedMC 7b9fb880f4 Also changed the color for the /iris o paste cmd 2023-09-25 15:02:33 +02:00
RePixelatedMC 2a9b0a54d9 Merge branch 'VolmitSoftware:master' into master 2023-09-25 14:37:08 +02:00
RePixelatedMC 373e9e9755 Fixed - /iris object undo
also added some colors
2023-09-25 14:30:21 +02:00
BuildTools 4115dd5797 remove debug info 2023-09-18 13:48:19 +02:00
BuildTools 57bfd251dc better fix for jigsaw loot 2023-09-18 13:45:03 +02:00
Brian Neumann-Fopiano d324790f66 forgot the v+ lol 2023-09-17 15:54:08 -04:00
Brian Fopiano 99d3dba440 Merge pull request #1026 from CocoTheOwner/jigsawRotationFix
Disable initial rotation if set to true
2023-09-17 13:47:51 -04:00
Brian Fopiano 5a44e79ad1 Merge pull request #1022 from CrazyDev05/jigsaw_loot_fix
Jigsaw loot fix
2023-09-17 13:47:26 -04:00
CocoTheOwner 86808017db Disable initial rotation if set to true 2023-09-06 18:24:15 +02:00
CrazyDev22 f735db9843 Fix not finding IrisObjectPlacement for jigsaw structures 2023-09-02 10:31:34 +02:00
CrazyDev22 5a333c23ac Fix Exact BlockData Filter 2023-09-02 10:30:44 +02:00
Brian Neumann-Fopiano 4d2d51edfa v+ 2023-08-31 14:50:49 -04:00
Brian Fopiano dcfd5a0cd8 Merge pull request #1010 from theDAVID543/master
fix bug
2023-08-31 14:41:28 -04:00
theDAVID543 a821c0da50 fix bug 2023-08-18 11:53:06 +08:00
Brian Neumann-Fopiano 804147e2c4 overworld bump 2023-08-12 11:47:42 -04:00
Brian Neumann-Fopiano ed346291f9 oops v+ 2023-07-29 06:13:42 -04:00
Brian Fopiano 2190f6eff8 Merge pull request #1003 from CocoTheOwner/better-pregen-pred
Predict the ETA for pregeneration more rapidly
2023-07-29 03:04:07 -07:00
Brian Fopiano ce30e24e42 Merge pull request #1002 from CocoTheOwner/pregen-center-rescale
Fixes a scaling issue with pregen center
2023-07-29 03:03:27 -07:00
Brian Fopiano d5e13541c8 Merge pull request #1000 from christolis/fix-macos-build-error
Fix unresolved dependency error
2023-07-29 03:03:11 -07:00
Brian Fopiano 2e707adaa0 Merge branch 'master' into fix-macos-build-error 2023-07-29 03:03:00 -07:00
Brian Fopiano 4d8e36cb05 Update bug.yml 2023-07-24 04:34:17 -04:00
Brian Neumann-Fopiano 73b710cdde oraxen update 2023-07-16 18:46:31 -04:00
Brian Neumann-Fopiano 09182bf3f3 V+ for iris overworld tweaks 2023-07-13 07:09:09 -04:00
CocoTheOwner 9a945837cd Actually just after 1 region is generated switch 2023-07-10 23:09:02 +02:00
CocoTheOwner b64c821ef4 smooth into the warm-up generator 2023-07-10 23:00:28 +02:00
CocoTheOwner a9ab3bc519 Predict pregen better 2023-07-10 22:49:50 +02:00
CocoTheOwner 94292cea6e shift pregen center to blocks instead of regions 2023-07-10 18:43:49 +02:00
Christolis b5fdeb2a02 build: add extra dependency 2023-07-08 00:26:43 +03:00
Brian Neumann-Fopiano fa3ac36e8c v+ 2023-07-03 23:27:18 -04:00
Brian Neumann-Fopiano 6632d1b65f inc the git 2023-07-03 23:24:55 -04:00
Brian Neumann-Fopiano 8805693800 1.20.1 inital update 2023-06-13 12:18:48 -04:00
Brian Neumann-Fopiano 93db7ee0ee coco's other pr 2023-06-11 14:09:15 -04:00
Brian Fopiano 9ba4dd4099 Merge pull request #995 from CocoTheOwner/printWrongSpawner
Better print information about wrong spawner on marker
2023-06-11 14:00:09 -04:00
Brian Neumann-Fopiano 36efacf43c inc dim 2023-06-11 13:36:35 -04:00
Brian Neumann-Fopiano d9bf91afa5 1.20 2023-06-11 13:05:23 -04:00
Brian Neumann-Fopiano b0c96be841 1.20 Baseline 2023-06-07 16:50:44 -04:00
Sjoerd van de Goor 673b08f174 Merge branch 'master' into printWrongSpawner 2023-06-06 19:19:47 +02:00
Sjoerd van de Goor 989778af26 print in case a wrong spawner is specified 2023-06-06 19:18:47 +02:00
Brian Neumann-Fopiano f5a3a7eb1a wm 2023-06-01 14:11:41 -04:00
Brian Neumann-Fopiano 7a97928c21 added a configurable 2023-05-31 12:19:32 -04:00
Brian Neumann-Fopiano 12a6d022cf other updates! 2023-05-16 23:38:37 -04:00
Brian Neumann-Fopiano e3d2dfa99e Merge remote-tracking branch 'origin/master' 2023-05-16 23:32:12 -04:00
Brian Neumann-Fopiano 35cc39e9e2 cleanup plugin 2023-05-16 23:32:01 -04:00
Brian Fopiano 006b0b4a03 Update README.md 2023-04-29 18:52:20 -04:00
Brian Neumann-Fopiano ec939b9f78 updated dependencies 2023-04-25 12:21:11 -04:00
Brian Neumann-Fopiano 7a94f53735 V+ 2023-04-20 00:12:20 -04:00
Brian Neumann-Fopiano a9efe146ba Probably fixed * 2023-04-13 21:28:46 -04:00
Vatuu 956e2f6b06 Fixed Oraxen integration. 2023-04-13 23:24:12 +02:00
Brian Neumann-Fopiano 72623e0acf V+ 2023-04-12 18:05:47 -04:00
Brian Neumann-Fopiano e0f673bc3c Overworld Tag update to 3001 2023-04-12 18:05:08 -04:00
Brian Neumann-Fopiano fe40f12d2e Merge remote-tracking branch 'origin/master' 2023-04-12 18:02:18 -04:00
Brian Fopiano 0fda7a8506 Merge pull request #990 from CrazyDev05/oraxen_implementation_fix 2023-04-11 00:06:06 -04:00
Brian Neumann-Fopiano 22887da769 Added IPECTER's nms line
should fix compiling
2023-04-10 18:14:57 -04:00
Vatuu a1e0a8ffc1 Fixed ItemsAdder integration. 2023-04-10 19:26:29 +02:00
CrazyDev22 62010116d7 change split limit from '1' to '2' 2023-04-10 13:05:09 +02:00
Brian Neumann-Fopiano 6ebcd02ae6 Fixed the pregen error! 2023-04-08 00:26:05 -04:00
Brian Fopiano 051015656a Merge pull request #986 from CocoTheOwner/re-slope-condition
Patches an issue with slope conditions when placing with commands
2023-04-04 14:00:19 -04:00
Sjoerd van de Goor ee082762c6 Update object placer to ignore some stuff when using commands 2023-04-03 21:22:08 +02:00
Brian Fopiano e967b5e052 Merge pull request #982 from CocoTheOwner/re-slope-condition
Reimplement slope condition
2023-04-02 15:39:42 -04:00
Brian Neumann-Fopiano 36505e2fa1 forgot v+ 2023-04-02 15:19:58 -04:00
Vatuu 502aa054f6 🧌 Fixed Color and Seed Issue. 2023-04-02 21:10:44 +02:00
Sjoerd van de Goor cc5a880fd7 reimplement slope condition 2023-03-29 21:36:18 +02:00
Vatuu 6f9ad8b0eb Update to 1.19.4 2023-03-27 23:24:58 +02:00
Vatuu ac6ab74d48 🧌 CustomItems support is pretty scuffed. Make it a gradle dependency already. 2023-03-27 23:22:15 +02:00
Brian Neumann-Fopiano 807ed2b247 gradelized patch
I'm not including this until it can be gradle-ized for sake of public compilation
2023-03-16 14:10:32 -04:00
Brian Neumann-Fopiano 367de5a8fd v+ 2023-03-16 14:03:54 -04:00
Vatuu 5bf2da714a Whoops. 2023-03-13 19:28:02 +01:00
Vatuu cc90f42deb Implement CustomItems support. 2023-03-13 13:19:37 +01:00
Vatuu 20ceaead09 Fixed Oraxen and IA Integration. 2023-03-10 19:08:46 +01:00
Brian Fopiano 3ec2f8fb7b Merge pull request #962 from CocoTheOwner/render-continent-v2
Replace continent renderer with biomeStream
2023-03-05 19:18:54 -05:00
Brian Fopiano 6c5fac154e Merge pull request #975 from CocoTheOwner/mm-fix
Fixed mythicmobs and implements it properly.
2023-03-05 19:18:10 -05:00
Sjoerd van de Goor 540ab8f0d2 If special then unknown default should still be skipped 2023-03-03 23:57:37 +01:00
Brian Fopiano 17d2ac8d70 Merge pull request #973 from CocoTheOwner/spawn-entity
Entity spawn command for testing
2023-03-03 14:38:23 -08:00
Brian Fopiano a6ebdead19 Merge pull request #966 from CocoTheOwner/update-world-nice
nicer command :)
2023-03-03 14:37:31 -08:00
Brian Fopiano aa7631ecd0 Merge pull request #974 from CocoTheOwner/copypastafix
Fix copy paste of string gone wrong
2023-03-03 14:37:04 -08:00
Sjoerd van de Goor 4e138cad9f Why 2023-03-03 23:23:19 +01:00
Sjoerd van de Goor 7e55b5fcee Import MM 2023-03-03 23:23:15 +01:00
Sjoerd van de Goor e19e5278b4 shouldve seen that, oops. 2023-03-03 23:01:09 +01:00
Sjoerd van de Goor dfcd2dc83e lol 2023-03-03 21:59:42 +01:00
Sjoerd van de Goor 8775f842e6 Spawn command 2023-03-03 21:57:13 +01:00
Sjoerd van de Goor 7b07a4ba6c Location context needed to spawn entities 2023-03-03 21:56:33 +01:00
Sjoerd van de Goor d78e5973e9 nicer command :) 2023-02-23 19:13:14 +01:00
CocoTheOwner 110d296184 Replace bridge with stream used in /ir what biome 2023-02-17 11:51:27 +01:00
CocoTheOwner 23cd5c117b Replace continent renderer with bridgeStream 2023-02-17 11:25:26 +01:00
CocoTheOwner 131c4692bc Remove DEFER from InferredType
Unused
2023-02-17 11:25:10 +01:00
Brian Neumann-Fopiano f6571367db v+ oops 2023-02-15 18:41:17 -05:00
Brian Fopiano f5c64c7480 Merge pull request #960 from CocoTheOwner/render-continent
Continent (i.e. heightmap + ocean/land division) render
2023-02-15 14:47:14 -08:00
Brian Fopiano 085f63a915 Merge pull request #961 from CocoTheOwner/slopes-rotation
Implements slope rotation
2023-02-15 14:46:48 -08:00
Brian Fopiano 3e022e1931 Merge pull request #954 from CocoTheOwner/slopes
Slope condition
2023-02-15 14:46:28 -08:00
Brian Fopiano 0dba3725ae Merge pull request #953 from CocoTheOwner/mm-set-stuff
MM should set its own stuff
2023-02-15 14:44:42 -08:00
Brian Fopiano 8bb409df4e Merge pull request #952 from CocoTheOwner/fix-image-math
Fix image mapping math
2023-02-15 14:44:09 -08:00
Brian Fopiano 603168a147 Merge pull request #951 from CocoTheOwner/fix-decree-conv
Actually check for origin on command invoke
2023-02-15 14:42:57 -08:00
Sjoerd van de Goor 2a95edd860 further documentation and cleaner code 2023-02-15 14:14:26 +01:00
Sjoerd van de Goor e94406fb45 Implement rotation, remove precise rotation. 2023-02-15 14:05:34 +01:00
Sjoerd van de Goor e5a7b5d0c6 Reformulate todo 2023-02-15 13:12:34 +01:00
Sjoerd van de Goor d6f816fe2f Flipped water / land 2023-02-15 13:00:13 +01:00
Sjoerd van de Goor c15d4a349f map to 255 range 2023-02-15 12:55:43 +01:00
Sjoerd van de Goor 3ce832583c Continent (i.e. heightmap + ocean/land division) render 2023-02-15 12:49:20 +01:00
Sjoerd van de Goor 5514fd2645 Proper ordering 2023-02-08 01:51:39 +01:00
Sjoerd van de Goor 66d07dcaca Revert "Slope condition"
This reverts commit d4c0e07b1d.
2023-02-08 01:35:18 +01:00
Sjoerd van de Goor c1cf8e88ee That is pretty necessary 2023-02-08 01:33:31 +01:00
Sjoerd van de Goor 684bd739b9 Slope rotation (unfinished) 2023-02-08 01:30:12 +01:00
Sjoerd van de Goor d4c0e07b1d Slope condition 2023-02-08 01:29:30 +01:00
Sjoerd van de Goor a6ea6fcfb2 better 2023-02-07 17:39:44 +01:00
Sjoerd van de Goor c366ec0c40 MM should set its own stuff 2023-02-07 17:26:06 +01:00
CocoTheOwner 8d715e2e4e x != z 2023-02-06 15:43:27 +01:00
CocoTheOwner dea3ec80ac Fix image mapping math
Fixes snippet code, prevents an NPE, fixes centered for coordinateScale scaled image noises, fixes tiling on negative numbers (-1 % 2 = -1, a free fuck you from java)
2023-02-06 15:39:28 +01:00
CocoTheOwner 4053f05ba9 Actually check for origin on command invoke 2023-02-06 14:58:17 +01:00
Brian Neumann-Fopiano ef07ec2c62 Datapack Shenanigans 2023-02-04 14:38:30 -05:00
Brian Neumann-Fopiano 0ffc64231b V+ 2023-01-17 12:38:32 -05:00
Brian Neumann-Fopiano fe112c3ba5 Actuator Cleanup 2023-01-17 12:35:40 -05:00
Brian Neumann-Fopiano 2fa03aac3b Tweaked Min/Max Values 2023-01-17 12:35:26 -05:00
Brian Neumann-Fopiano 6ac5ad880d Simplified 2023-01-17 12:35:13 -05:00
Brian Neumann-Fopiano 54f14392d9 Removed Redundant World Init 2023-01-17 12:35:03 -05:00
Brian Fopiano c7ecdaf8a8 Merge pull request #944 from ColorsWind/Development
fix issue with `bukkit.yml` world-container option
2023-01-15 12:02:28 -08:00
ColorsWind 497c24a3b6 fix issue with bukkit.yml world-container option 2023-01-05 15:58:57 +08:00
Brian Fopiano 00aa26e602 oops 2022-12-28 13:27:24 -05:00
Brian Fopiano 3796381156 Merge remote-tracking branch 'origin/Development' into Development 2022-12-28 13:23:05 -05:00
Brian Fopiano b0c5700cf5 Experimental Speedy things with New java 2022-12-28 13:23:00 -05:00
Brian Fopiano 78a4b1d2ce Clean 2022-12-28 13:05:32 -05:00
Brian Fopiano e5e6b9848f Merge pull request #943 from simon44556/master
- Update ender eye usage to ignore portal frame clicks
2022-12-25 18:06:14 -05:00
simon44556 3e36f57f14 - Update ender eye usage to ignore portal frame clicks 2022-12-25 18:00:17 +01:00
Brian Fopiano d794ede4d8 Vwoop 2022-12-22 15:09:37 -05:00
Brian Fopiano 7a4d97d76a Version Check? 2022-12-22 14:38:20 -05:00
Brian Fopiano e76ab1d367 . 2022-12-22 14:23:52 -05:00
Brian Fopiano 9731feff7f hmmm 2022-12-19 20:36:36 -05:00
cyberpwn 4ce790082d Gource is cool 2022-12-11 12:32:05 -05:00
Brian Fopiano 065b748036 Merge pull request #938 from VolmitSoftware/Development
Development
2022-12-09 18:09:21 -08:00
Brian Neumann-Fopiano 76891d246f HUGE changes 2022-12-09 18:05:47 -08:00
Brian Neumann-Fopiano 0832007991 it compiles, not working 2022-12-08 19:40:13 -08:00
Brian Fopiano 87302a046c Merge pull request #933 from VolmitSoftware/Development
Development
2022-11-30 10:07:07 -08:00
Brian Neumann-Fopiano f0f7453b32 V+ 2022-11-30 10:05:34 -08:00
Brian Neumann-Fopiano 2991bea248 updated pack 2022-11-30 10:04:25 -08:00
Brian Neumann-Fopiano 673b42c2f5 Papi and Biome location better 2022-11-30 08:55:27 -08:00
Brian Neumann-Fopiano 7d994e27e3 "A Biome" changed to be more desctiptive 2022-11-30 08:55:05 -08:00
Brian Fopiano 5f2b82aac7 Merge pull request #926 from VolmitSoftware/Development
Development
2022-11-12 13:34:06 -08:00
Brian Neumann-Fopiano af1a03cb67 Optimization Overhaul 2022-11-12 13:31:39 -08:00
Vatuu 85fbbeca9d Preparing the POI System. 2022-11-05 15:12:17 +01:00
Brian Fopiano a64272620f Merge pull request #920 from VolmitSoftware/Development
Development
2022-10-28 13:14:47 -07:00
Brian Neumann-Fopiano ef9966d02b V+ 2022-10-28 13:07:11 -07:00
Vatuu a28e5bb38a Updated overworld version. 2022-10-28 22:03:23 +02:00
Vatuu 34670759e9 Fixed eyes of enders not properly targeting strongholds. 2022-10-28 22:02:05 +02:00
Vatuu 7dd1d3881a Fixed strongholds not generating properly. 2022-10-28 22:01:23 +02:00
Brian Fopiano 7b93542014 Merge pull request #916 from VolmitSoftware/Development
Grad
2022-10-21 02:01:15 -07:00
Vatuu ea1ac0a154 V+ 2022-10-21 08:38:56 +02:00
Vatuu 35c7068fa6 Merge remote-tracking branch 'origin/master' 2022-10-21 08:36:36 +02:00
Vatuu db4cbb2ea6 Updated overworld tag. 2022-10-21 08:36:11 +02:00
Brian Neumann-Fopiano fa7c073999 Grad 2022-10-12 03:13:25 -07:00
Brian Fopiano 72a077662d Merge pull request #915 from VolmitSoftware/Development
V+
2022-10-12 02:54:10 -07:00
Brian Neumann-Fopiano 137abb04b2 V+ 2022-10-12 02:52:49 -07:00
Vatuu 211b15332d Code Cleanup, the second. 2022-10-12 07:13:03 +02:00
Brian Fopiano f6fbcade17 Merge pull request #913 from VolmitSoftware/Development
Development
2022-10-07 18:58:13 -07:00
Brian Fopiano 1ef115fee8 V+ 2022-10-07 18:51:45 -07:00
Vatuu 7973444fc5 Code Cleanup. 2022-10-07 23:58:21 +02:00
Vatuu 2bdb0bfa69 Fixed #901: Longs are now properly parsed by decree. 2022-10-05 04:20:08 +02:00
Vatuu bfb6f82ea3 Fixed /iris create not installing datapacks. 2022-10-05 04:16:50 +02:00
Brian Fopiano 7729ce3753 Merge pull request #911 from VolmitSoftware/Development
Development
2022-09-30 21:19:06 -07:00
Brian Fopiano f6ea171669 V+ 2022-09-30 21:18:58 -07:00
tanticle ff081ebc11 Merge remote-tracking branch 'origin/Development' into Development 2022-09-28 02:41:14 +02:00
tanticle 0818f971fe Added #816: Whitelist and blacklist for decorators. 2022-09-28 02:40:52 +02:00
tanticle 27070f44c7 Fixed block drops. 2022-09-28 02:00:34 +02:00
Brian Fopiano 8331cbe375 Merge pull request #910 from VolmitSoftware/Development
Hotfix
2022-09-25 16:43:11 -07:00
Vatuu f533ec34b0 Hotfix 2022-09-25 16:42:36 -07:00
Vatuu 23d4fcb827 Hotfix because Brian did a bad 2022-09-26 01:09:08 +02:00
Brian Fopiano a248962f1b Merge pull request #909 from VolmitSoftware/Development
Development
2022-09-23 20:03:37 -07:00
Vatuu afa8fad8e9 V+ 2022-09-24 04:54:32 +02:00
Vatuu 4c423bb493 Resolved #878: Certain values in settings are no longer ignored. 2022-09-24 04:52:42 +02:00
Vatuu e660fe9e1e Resolved #892: Snow no longer replaces objects. 2022-09-24 04:43:47 +02:00
Vatuu 23c1d12e73 Removed deprecated repository. 2022-09-23 14:58:39 +02:00
Brian Fopiano 9cf2785626 Merge pull request #908 from VolmitSoftware/iris-go-fast-but-not-stable
Pregen improvements
2022-09-21 07:27:20 -07:00
Brian Fopiano b8b65c7e3e Merge pull request #907 from VolmitSoftware/Development
Development
2022-09-21 07:13:01 -07:00
Vatuu dcb467280a V+ 2022-09-21 01:52:20 +02:00
Vatuu 6aa802b42d Merge remote-tracking branch 'origin/Development' into Development 2022-09-21 01:49:58 +02:00
Vatuu d78f6ca589 Resolved #900: Remaining time in days corrected. 2022-09-21 01:48:53 +02:00
Vatuu 493f8e0cc0 Resolved #897: Adjusted Y-Offset 2022-09-21 01:12:35 +02:00
Vatuu 119563c553 Resolved #871: Removed /iris std clean 2022-09-20 23:55:18 +02:00
cyberpwn 2d61e6af66 Pregen improvements 2022-09-16 09:09:23 -04:00
Brian Fopiano aa1be934a9 Merge pull request #899 from VolmitSoftware/iris-go-fast-but-not-stable
Jesus Fucking Christ
2022-09-16 02:05:01 -07:00
Brian Fopiano f9c98b0a60 Needs to be Sync now 2022-09-16 01:52:17 -07:00
Brian Fopiano bd66fa3bf5 Tag Bump 2022-09-16 00:50:38 -07:00
Brian Fopiano 380f191f6a V+ 2022-09-16 00:48:27 -07:00
cyberpwn 7e217b5fba More permits 2022-09-16 03:40:31 -04:00
cyberpwn c4a516a858 f 2022-09-16 03:37:36 -04:00
cyberpwn bdf181e348 ff 2022-09-16 03:28:47 -04:00
cyberpwn f4ea1343b4 m 2022-09-16 03:27:32 -04:00
cyberpwn a37ccddd38 f 2022-09-16 03:26:23 -04:00
cyberpwn 243ef8c0be f 2022-09-16 03:24:56 -04:00
cyberpwn 00d099383b Fix 2022-09-16 03:22:56 -04:00
cyberpwn dda3f0b8e9 Mhmmmmm 2022-09-16 03:16:57 -04:00
cyberpwn 00f4d8b1ee Hmm? 2022-09-16 03:16:04 -04:00
cyberpwn 2f5198d533 Safe 2022-09-15 02:35:34 -04:00
cyberpwn 148261f876 Dirrrrrrrrrrrrty 2022-09-15 02:32:29 -04:00
cyberpwn 60843b3bb9 f 2022-09-14 13:30:39 -04:00
DanMB 2ad9a525bd Biome holder injection post gen 2022-09-14 03:56:06 -04:00
DanMB 78e9f21439 f 2022-09-13 23:32:11 -04:00
DanMB a66d60eaea woah 2022-09-13 23:06:52 -04:00
DanMB 3aa3c13477 Test speed 2022-09-13 22:55:26 -04:00
cyberpwn cc850522e6 BRRRRRRRRRRRRRRRRRRR 2022-09-13 09:29:07 -04:00
Brian Fopiano 6d28f57f88 Merge pull request #894 from VolmitSoftware/master
d
2022-09-12 16:00:36 -07:00
Brian Fopiano 563e0a7cd4 V+ 2022-09-12 16:00:14 -07:00
Brian Fopiano 2f2f857e98 Merge pull request #893 from VolmitSoftware/Development
Development
2022-09-12 15:59:20 -07:00
cyberpwn 251c52a2ee Fix cave mod engine npe 2022-09-12 17:26:14 -04:00
cyberpwn 7528bd343b 3x 2022-09-12 07:06:45 -04:00
cyberpwn 06d9f279ac f 2022-09-11 23:00:15 -04:00
cyberpwn 044403b829 Fix incorrect object positions 2022-09-11 21:31:21 -04:00
Brian Fopiano 5a24dd3b49 Revert "Revert "Eat me""
This reverts commit 3189b3c7a5.
2022-09-11 21:00:55 -04:00
Brian Fopiano 3189b3c7a5 Revert "Eat me"
This reverts commit da777da476.
2022-09-11 21:00:38 -04:00
cyberpwn 3bf1c6a282 Merge remote-tracking branch 'origin/Development' into Development 2022-09-11 20:59:04 -04:00
cyberpwn 7c7309c3ab Unload mantle when not generating 2022-09-11 20:58:58 -04:00
Brian Fopiano da777da476 Eat me 2022-09-11 17:57:08 -07:00
cyberpwn 3d0c0a11ed Merge remote-tracking branch 'origin/Development' into Development 2022-09-10 02:04:58 -04:00
cyberpwn e545269b93 Even less waste work & prefetch caching 2022-09-10 02:03:59 -04:00
Brian Fopiano 141ca76647 Merge pull request #890 from VolmitSoftware/Development
Development
2022-09-09 18:05:56 -07:00
Brian Fopiano 94557830f5 v+ 2022-09-09 18:02:21 -07:00
Vatuu 49acb7faba Merge remote-tracking branch 'origin/Development' into Development 2022-09-09 21:42:53 +02:00
Vatuu 1904f67662 Fixed Issue #864: Mantle not retaining object placements. 2022-09-09 19:48:23 +02:00
Vatuu 5b2bf38344 Fixed Issue #884: Cache freaking out on global override. 2022-09-09 18:10:52 +02:00
cyberpwn 494c38a153 Merge remote-tracking branch 'origin/Development' into Development 2022-09-09 01:25:16 -04:00
cyberpwn dd4b85cbfe Fixes 2022-09-09 01:24:20 -04:00
Brian Fopiano 8f7b54a5a4 Merge pull request #889 from VolmitSoftware/Development
Development
2022-09-08 17:26:41 -07:00
Vatuu cec502340e V+ 2022-09-09 01:15:53 +02:00
Vatuu 64e27c7fb0 Disabled blockdrops as hotfix. 2022-09-09 01:15:07 +02:00
cyberpwn b2bbd31548 Disable multicore chunks 2022-09-07 17:07:34 -04:00
Brian Fopiano 17df92a07c Merge pull request #887 from VolmitSoftware/Development
Development
2022-09-06 20:55:18 -07:00
Brian Fopiano 2b749b5ab7 NMS update / Compile 2022-09-06 20:51:59 -07:00
cyberpwn 28e3402d88 r 2022-09-05 14:17:56 -04:00
cyberpwn d96bb061e0 Update repo 2022-09-05 14:14:50 -04:00
Brian Fopiano e6def804f8 Merge pull request #883 from VolmitSoftware/Development
Development
2022-09-02 17:48:22 -07:00
Vatuu a5be48c07c V+ 2022-09-02 23:35:38 +02:00
Vatuu fe3909f594 Fixed block drops not firing for biomes. 2022-09-02 23:32:51 +02:00
Vatuu f37d91a530 Fixed loot table object override not working. 2022-09-02 23:26:47 +02:00
Vatuu 614ef78771 Fixed reliance on outdated spigot api version. 2022-09-02 20:32:17 +02:00
Brian Fopiano fb294fc03c Merge pull request #879 from VolmitSoftware/Development
Changelog:

- Fixed Vines, Again! for Floors
- Merged stability changes
- Made the folder checking system recursive
2022-08-30 10:20:38 -07:00
Vatuu 8c7e7c3d48 Merge remote-tracking branch 'origin/Development' into Development 2022-08-30 19:15:16 +02:00
Vatuu 98b6280652 V+ 2022-08-30 19:15:05 +02:00
Vatuu 7c4c2d6382 Fixed autocomplete only going two dirs deep. 2022-08-30 19:14:28 +02:00
Vatuu 1a29ea302e Fixed vines not properly generating using decorators. 2022-08-30 18:06:17 +02:00
Brian Fopiano be39fce741 Merge pull request #876 from VolmitSoftware/Development
Changelog:

Fixed Versioning compilation issues
Forceblock nolonger Crashes
Forceblock Works now (as intended, as far as we can tell)
2022-08-26 16:10:38 -07:00
Brian Fopiano 4fed2e6e5f Merge branch 'master' into Development 2022-08-26 16:10:26 -07:00
Vatuu 14ae5809b0 Forceblock now properly works. 2022-08-26 19:05:33 +02:00
Vatuu 0f66aa47c9 Reverted generator change 2022-08-26 18:36:09 +02:00
Vatuu cf85d300f4 Solving seed collisions on generators? 2022-08-26 17:54:56 +02:00
Vatuu 91d60e56df Fixed jar using the wrong version. 2022-08-26 17:27:49 +02:00
Brian Fopiano 1d3681beb4 Merge pull request #872 from VolmitSoftware/Development
Development
2022-08-23 18:51:22 -04:00
Vatuu 0b403cf329 Merge remote-tracking branch 'origin/Development' into Development 2022-08-23 17:22:17 +02:00
Vatuu 8ba75899e3 Fixed block drops. 2022-08-23 17:21:54 +02:00
Vatuu 4d4ed72159 _Maybe_ fixed mantle not retaining after restart? 2022-08-23 17:19:16 +02:00
Vatuu b72bf072b3 _Maybe_ fixed mantle not retaining after restart? 2022-08-23 17:11:28 +02:00
Vatuu 4edcb54b31 Fixed /iris what biome not working. 2022-08-23 15:51:04 +02:00
Brian Fopiano eae7fc0e53 V+ 2022-08-18 19:00:47 -07:00
Brian Fopiano fddacd4410 Merge pull request #863 from VolmitSoftware/Development
Development Update
2022-08-18 19:00:04 -07:00
Brian Fopiano de6c4b9a30 Merge remote-tracking branch 'origin/Development' into Development 2022-08-17 20:22:04 -07:00
Brian Fopiano 6990e34138 Removed SoftDepends, Fixed Auth 2022-08-17 20:21:59 -07:00
DanMB d9883697ef Change dep to match nms version var 2022-08-16 19:14:35 -07:00
Brian Fopiano 05a26ff8b2 Revert "helping with version Nomners"
This reverts commit 0765c21cef.
2022-08-16 16:01:36 -07:00
Brian Fopiano 84bb082d44 Merge pull request #856 from VolmitSoftware/Development
Development
2022-08-16 13:06:00 -07:00
Brian Fopiano a0db94d84f Merge branch 'master' into Development 2022-08-16 13:05:54 -07:00
Brian Fopiano 0367b4ecd7 v+ 2022-08-16 13:04:27 -07:00
Vatuu dbc425dce6 Added autocomplete for enchantment types. 2022-08-16 16:08:25 +02:00
Vatuu 116c017c6d Fixed enchantments not applying to loot. 2022-08-16 15:45:48 +02:00
Vatuu 9c387475f0 Cleaned up IrisLoot. 2022-08-16 15:21:18 +02:00
Brian Fopiano 0765c21cef helping with version Nomners 2022-08-13 03:20:42 -07:00
Brian Fopiano 2871038584 v+ 2022-08-12 15:43:39 -07:00
Brian Fopiano 340885f939 Merge pull request #849 from VolmitSoftware/Development
Development
2022-08-12 15:43:09 -07:00
Vatuu 1e32e47f54 Merge remote-tracking branch 'origin/Development' into Development 2022-08-13 00:37:59 +02:00
Vatuu 3c759f3b01 Kinda fixed vine issues with PAINT. 2022-08-13 00:37:42 +02:00
DanMB 6615f34d20 Merge remote-tracking branch 'origin/Development' into Development 2022-08-12 08:05:53 -07:00
DanMB 3a4aac1ee4 Get all 2022-08-12 08:05:48 -07:00
Brian Fopiano 4d6c092615 Performance... 2022-08-11 18:34:38 -07:00
DanMB 93421a1dc9 An attempt 2022-08-11 18:18:00 -07:00
Brian Fopiano 209458a856 Merge pull request #847 from VolmitSoftware/Development
Development update
2022-08-09 13:50:26 -07:00
DanMB 82bd94620e Lock biomes first before generating to help cache 2022-08-07 16:10:49 -07:00
DanMB af9f017871 Hold your horses pregenerator 2022-08-07 16:09:36 -07:00
Brian Fopiano 2f8922ea87 Updating Version and NMS 2022-08-07 14:14:39 -07:00
Brian Fopiano 0ea936f3fe Merge pull request #845 from VolmitSoftware/Development
Development
2022-08-04 08:50:17 -07:00
Brian Fopiano d1870ee0a8 V+ 2022-08-04 08:43:03 -07:00
Vatuu d2151690ad Fixed pregen memory leak. 2022-08-04 00:27:33 +02:00
Brian Fopiano dff373e7d5 Merge pull request #843 from CocoTheOwner/sortedNoise
sort noise
2022-07-30 16:23:45 -07:00
CocoTheOwner 0aec2a664f sort noise 2022-07-30 22:19:02 +01:00
Brian Fopiano 723b696393 Merge pull request #838 from VolmitSoftware/Development
Development
2022-07-29 10:04:58 -07:00
Brian Fopiano f5f89eb4e4 Merge branch 'master' into Development 2022-07-29 10:04:53 -07:00
Brian Fopiano df15332c7f Merge pull request #832 from CocoTheOwner/noWarn
Remove warning on create
2022-07-29 10:04:22 -07:00
Vatuu 61d9e5a869 V+ 2022-07-29 19:03:18 +02:00
Brian Fopiano 9628b66a97 Merge pull request #837 from VolmitSoftware/Development
Development Update
2022-07-29 10:02:04 -07:00
Vatuu 5e041366d4 Update to 1.19.1 2022-07-28 18:29:01 +02:00
Vatuu 3ecae8d72c Removed debug printout. 2022-07-28 17:45:51 +02:00
Vatuu 5709ce9d82 Fixed the QOL command being a bitch. 2022-07-28 15:54:31 +02:00
Vatuu ce3c334ac5 Fixed vines and derivatives not placing properly in objects. 2022-07-27 19:35:18 +02:00
Vatuu 2ed5aa8730 Removed leftover debug message. 2022-07-26 21:41:06 +02:00
Vatuu 8480a63dda Fixed sculk veins not being recognized as vines. 2022-07-26 21:40:28 +02:00
CocoTheOwner 132a1695f3 Bey 2022-07-24 14:20:12 +01:00
Brian Fopiano 21775630c3 Merge pull request #830 from VolmitSoftware/Development
Development
2022-07-21 19:38:44 -07:00
Brian Fopiano eac2ef7c6d V+ 2022-07-21 19:38:14 -07:00
Vatuu 386997f646 Added forceBlock field for surface decorator. 2022-07-22 02:18:37 +02:00
Vatuu 637b90ad62 Fixed stilted facing blocks not rotating. 2022-07-22 02:06:44 +02:00
Vatuu 95d755c2ec Fixed underwater structures not being waterlogged. 2022-07-22 02:06:00 +02:00
Brian Fopiano 781aeebef6 Merge pull request #826 from CDFN/patch-1
Update bug template for 1.19
2022-07-17 13:17:56 -07:00
Brian Fopiano 5e133fd51a Merge pull request #827 from CocoTheOwner/commands
Add /iris remove command
2022-07-17 13:03:58 -07:00
Brian Fopiano 85f62a3b9d we really cant do this override 2022-07-17 11:08:02 -07:00
DanMB a32de58c8b Lazy pregen 2022-07-17 08:52:56 -07:00
Brian Fopiano 1cb2ea6c17 Merge pull request #829 from VolmitSoftware/Development
Development
2022-07-16 15:46:30 -07:00
CocoTheOwner 6a37ab7af2 sync world unload 2022-07-16 12:39:42 +02:00
CocoTheOwner e0f0aaf767 Add remove command 2022-07-16 12:36:28 +02:00
Vatuu 3d7c5b050a Revert "Revert "Added convenience command.""
This reverts commit 6bc57c255f.
2022-07-16 12:33:39 +02:00
Vatuu 6bc57c255f Revert "Added convenience command."
This reverts commit 6b32eb3441.
2022-07-16 11:53:40 +02:00
Vatuu b82edfe688 V+ 2022-07-16 02:55:59 +02:00
Vatuu 6b32eb3441 Added convenience command. 2022-07-16 02:52:06 +02:00
Vatuu 23a07fa8a5 Fixed vines and glow lichen not generating properly. 2022-07-16 02:41:03 +02:00
Vatuu b019faedd2 Made the creator not error without a sender. 2022-07-15 20:02:26 +02:00
Bartosz Stefańczyk 4430433a10 Update bug template for 1.19 2022-07-15 16:05:15 +02:00
Brian Fopiano f52cd29e7b Merge pull request #821 from VolmitSoftware/Development
Development
2022-07-07 14:23:57 -07:00
Vatuu 6a44e593a6 V+ 2022-07-07 23:04:32 +02:00
Vatuu f7065fe034 Datenpackete werden nun in allen registrierten Welten generiert. 2022-07-07 22:54:54 +02:00
Vatuu 0a247956f7 Fixed colour and printout. 2022-07-07 22:28:59 +02:00
DanMB 03836acded ffff 2022-07-07 13:12:59 -07:00
Brian Fopiano b5fb277982 Merge pull request #819 from VolmitSoftware/Development
Development update 2.2.2
2022-07-01 16:11:20 -07:00
Vatuu e523d3c166 V+ 2022-07-02 01:04:39 +02:00
Vatuu 41477e4aa6 Made the overworld use releases instead. (Hack) 2022-07-02 01:03:18 +02:00
Vatuu fbaf42a8c4 Merge remote-tracking branch 'origin/Development' into Development 2022-07-01 21:00:17 +02:00
Vatuu 1c3668047b Fixed PlaceholderAPI issues. 2022-07-01 20:59:54 +02:00
Vatuu 7d78c69b6e /iris create now registers to bukkit.yml 2022-07-01 18:40:13 +02:00
Brian Fopiano 43bd96a679 Merge pull request #810 from VolmitSoftware/Development
Development
2022-06-25 14:08:37 -07:00
Brian Fopiano a87c83dae2 V+ 2022-06-25 14:06:16 -07:00
cyberpwn 490d2b69e3 uf 2022-06-25 16:16:53 -04:00
DanMB 7eac936431 Merge remote-tracking branch 'origin/Development' into Development 2022-06-25 16:00:53 -04:00
DanMB 588b261d52 df 2022-06-25 16:00:48 -04:00
DanMB daf2cf02b3 d 2022-06-25 15:59:40 -04:00
cyberpwn b9d97f2434 Goes faster 2022-06-25 15:47:34 -04:00
Vatuu 1467dc963d Changed the default download branch to be in line with GHs new standard. 2022-06-21 19:05:01 +02:00
Vatuu 89bf687fc4 Fixed some wrong descriptions. 2022-06-21 18:58:01 +02:00
Vatuu 2e7c80562b Added MIN_STILT, CENTER_STILT and derivatives.
Made stilt override use a palette.
Added yMax and yRand.
Consolidated stilt settings into a object.
2022-06-21 18:57:33 +02:00
Brian Fopiano 5d54b82be0 Spigot Watchdog Fixed 2022-06-20 17:42:10 -07:00
Brian Fopiano 2ae1df4d42 V+ 2022-06-17 09:57:07 -07:00
Brian Fopiano 26782e6f0c Paper shit up 2022-06-16 00:15:35 -07:00
Vatuu cc84d5b562 Fixed noiseOverlay. 2022-06-12 10:39:41 +02:00
Vatuu 8bb1efdca3 Merge remote-tracking branch 'origin/Development' into Development 2022-06-12 08:11:14 +02:00
Vatuu 7a5794c8e2 Fixed MIN_HEIGHT. 2022-06-12 08:11:02 +02:00
Vatuu 9dd20661f5 Added stilt override. 2022-06-12 08:10:07 +02:00
Brian Fopiano ad0ab66711 Merge remote-tracking branch 'origin/Development' into Development 2022-06-11 15:58:11 -07:00
Brian Fopiano 82396b5145 eew 2022-06-11 15:58:07 -07:00
Vatuu 246e974dc1 Fixed FAST_MIN_HEIGHT being messed up. 2022-06-10 17:43:14 +02:00
Vatuu 203b6a4b4f Fixed loading bar not having the coloured pulse effect. 2022-06-10 15:12:56 +02:00
Vatuu f204f3a209 It works! 2022-06-10 11:35:03 +02:00
Vatuu ba8e060370 It """works""". 2022-06-09 22:09:32 +02:00
Vatuu c51172acbc Updated kyori. 2022-06-09 12:51:33 +02:00
Brian Fopiano 39100a5011 Port WIP 2022-06-08 14:46:18 -07:00
DanMB fcee61a703 fg 2022-06-04 23:32:01 -07:00
DanMB ce8cc2cf37 Allow paper multicore to call without locking 2022-06-04 22:19:36 -07:00
DanMB 3b5bdcfb51 Merge remote-tracking branch 'origin/master' 2022-06-04 21:50:27 -07:00
DanMB 4d67e97973 Revert "Revert "No cng thing ""
This reverts commit da09f4d6d8.
2022-06-04 21:50:24 -07:00
DanMB da09f4d6d8 Revert "No cng thing "
This reverts commit dbc7e2e0bf.
2022-06-04 21:49:21 -07:00
DanMB dbc7e2e0bf No cng thing 2022-06-04 21:48:37 -07:00
DanMB dd804b6665 It compiles 2022-06-04 21:42:15 -07:00
DanMB 33eb878834 Test imp 1 2022-06-04 21:08:49 -07:00
DanMB 2a2c456011 More prec 2022-06-04 21:06:38 -07:00
DanMB 05a12f3899 Bench 2022-06-04 21:04:37 -07:00
DanMB eb04710f71 Merge remote-tracking branch 'origin/master' 2022-06-04 20:53:37 -07:00
DanMB 257760453e . 2022-06-04 20:53:28 -07:00
Brian Fopiano d11baeb08a Merge pull request #800 from VolmitSoftware/Development
V+
2022-06-03 20:23:46 -07:00
Vatuu 47d322c993 V+ 2022-06-04 05:21:55 +02:00
Vatuu d316207a61 Added tile entity support + mantle. 2022-06-03 06:19:13 +02:00
Vatuu e6daabbb47 Working on block entity support. 2022-06-02 10:39:28 +02:00
Vatuu 53156449f1 Made the mantle retain object placements. 2022-06-01 20:35:44 +02:00
Brian Fopiano e3d4c32d03 Merge pull request #796 from VolmitSoftware/Development
**Changelog**

- Fixed a Memory Leak (There might be more, keep reports coming)
 - Fixed Iris wand selections (WE)
 - Added Iris Wand Worldedit compatibility [Make selection using Worldedit, then /Iris object want worldedit (/ir o w we) to then set your selection to a new iris wand]
2022-05-26 18:24:06 -07:00
Vatuu 9cd5c39bbe V+ 2022-05-27 03:17:58 +02:00
Vatuu a1fbf25465 Please don't break or leak again. 2022-05-27 01:54:21 +02:00
Vatuu ec74037f05 Added ability to create Iris Wand from WorldEdit selection. 2022-05-26 21:48:46 +02:00
Brian Fopiano fff650b766 Merge pull request #795 from VolmitSoftware/Development
**Changelog:**
- Fixed Startup spam, Worldheight spam, and Entity ID spam from happening anymore
- Removes Raid Error From Villagers (Persistent raid)
- Minheight, MaxHeight, and STilt are now working as intended
- Fixed generation errors causing Holes in bedrock 
- generic math fixes internally

**This will require a world update to fix the Bedrock problems**
2022-05-21 20:26:19 -07:00
Vatuu bae2a18e24 V+ 2022-05-22 05:18:13 +02:00
Vatuu fd8a7ebb4c Properly fixed FAST_MAX_HEIGHT. 2022-05-22 05:08:28 +02:00
Vatuu 4a3a1491d4 Cleanup. 2022-05-22 04:25:39 +02:00
Vatuu 9aa2c5ed8a SPACE 2022-05-22 03:23:20 +02:00
Vatuu d5699a7472 Totally didn't replace MIN_HEIGHT instead of FAST_MAX_HEIGHT 💀 2022-05-22 03:22:19 +02:00
Vatuu df98f8f626 Added filter for duplicated entity uuid. 2022-05-22 03:07:46 +02:00
Vatuu d37b298f07 Fixed bad math for MAX_HEIGHT and FAST_MAX_HEIGHT. 2022-05-22 03:02:50 +02:00
Vatuu 62f7fc17d1 Suppressed persistent raid error. 2022-05-21 18:41:04 +02:00
Brian Fopiano 6e4929246d Merge pull request #794 from VolmitSoftware/master
upstream merge
2022-05-20 19:48:19 -07:00
Vatuu 909461975a Chat filter. 2022-05-21 04:47:12 +02:00
DanMB dec191e5e7 Web hate space 2022-05-19 16:23:11 -07:00
DanMB 07b0cd6f5a Img2 2022-05-19 16:20:25 -07:00
Brian Fopiano b7a892ecfe V+
Chonker of an update
2022-05-19 15:44:56 -07:00
Brian Fopiano 79d16102fa Merge pull request #789 from CocoTheOwner/fixDontUse
Replace MV with bukkit
2022-05-19 15:43:35 -07:00
Vatuu 67efdb47aa Custom dimension info on splash. 2022-05-17 20:15:02 +02:00
Vatuu 751c233b49 Merge remote-tracking branch 'origin/Development' into Development 2022-05-17 19:40:53 +02:00
Vatuu f51734fea0 Fixed fast_min_height? 2022-05-17 19:40:16 +02:00
DanMB 516a7111a7 f 2022-05-16 23:34:58 -07:00
DanMB 1e3ff59b26 Placement options removals 2022-05-16 23:23:42 -07:00
Vatuu 4c48f812d5 Dust of Revealing Pt.3 💀 2022-05-16 21:55:47 +02:00
Vatuu a63a6f751f Dust of Revealing Pt.2, also Whoops. 2022-05-16 19:40:39 +02:00
Vatuu 923860a90b Version bump. 2022-05-16 18:10:59 +02:00
Vatuu 5a4ca2f907 Fixed stilting with edited data. 2022-05-16 17:49:49 +02:00
Vatuu ccc2ed0806 Fixed Dust of Revealing Pt.1 2022-05-16 17:33:15 +02:00
Vatuu 44c707e059 Stilting underwater works now. 2022-05-16 16:53:49 +02:00
Vatuu e7c458de3f Hot reload listener for datapacks and stilts underwater. 2022-05-15 22:10:17 +02:00
Vatuu 11b05b10a4 Removed a single empty line. 2022-05-15 21:28:12 +02:00
Vatuu d133ac0088 Refactor. 2022-05-15 20:28:11 +02:00
Brian Fopiano 8951fcdebd SHUT THE HELL UP
(no more hotloading error)
2022-05-15 00:13:42 -07:00
Vatuu ecbd5677ce Merge remote-tracking branch 'origin/Development' into Development 2022-05-15 01:27:07 +02:00
Vatuu e31b9c1a28 Fixed log error. 2022-05-15 01:26:52 +02:00
Brian Fopiano faa7b7c742 Object placement height now increased (need to make engine dependent) 2022-05-14 12:07:27 -07:00
Vatuu d08f326477 Renaming. 2022-05-14 20:31:23 +02:00
Vatuu 6500ed2075 Made third party items work in loot tables. 2022-05-14 20:26:24 +02:00
Vatuu 56eb775a0f Merge remote-tracking branch 'origin/Development' into Development 2022-05-14 19:46:03 +02:00
Vatuu e225bcfb96 To make brian shut up. 2022-05-14 19:45:44 +02:00
Brian Fopiano 4be6abe416 2032 New MAX height 2022-05-14 10:38:52 -07:00
Vatuu c3de8b33de Fixed Oraxen, and custom dimension height. 2022-05-14 19:03:18 +02:00
Vatuu 5658d6709c Fixed Oraxen, and custom dimension height. 2022-05-14 18:29:57 +02:00
Vatuu 192538a741 ItemAdder link 2022-05-13 23:25:27 +02:00
Vatuu b8b9d7bf8c Added external block data registry. 2022-05-12 19:11:02 +02:00
CocoTheOwner 1db8603910 Replace MV with bukkit 2022-05-03 20:11:09 +02:00
Dan 1c5774fca5 Merge pull request #788 from VolmitSoftware/Development
2.0.7-1.18.2
2022-04-30 13:55:49 -07:00
DanMB 0b332b06b6 V+ 2022-04-30 13:55:18 -07:00
DanMB 0be9b049a3 Fix jigsaw editor 2022-04-30 13:54:42 -07:00
DanMB 6993639cc0 clean 2022-04-30 13:46:17 -07:00
DanMB 74172e3123 Reflective api additions 2022-04-21 10:57:19 -07:00
DanMB 6e1c4f682e abandon ship 2022-04-19 19:10:54 -04:00
DanMB af0d282a1f Matter loaders 2022-04-19 17:05:16 -04:00
DanMB 09bf15fa05 0.01% faster 2022-04-18 16:31:22 -04:00
DanMB 43f7d95b4c My folder 2022-04-18 14:57:44 -04:00
cyberpwn 8b536b1775 Merge remote-tracking branch 'origin/Development' into Development 2022-04-17 11:53:47 -04:00
cyberpwn 60a7f4bc35 Dumb stuff 2022-04-17 04:11:50 -04:00
Brian Fopiano 0ed0e0c983 Merge pull request #786 from VolmitSoftware/Development
not a word
2022-04-16 01:29:41 -07:00
Brian Fopiano f4b1ca9f21 not a word 2022-04-16 01:29:22 -07:00
Brian Fopiano 609104cfa8 Merge pull request #785 from VolmitSoftware/Development
Development
2022-04-16 01:23:44 -07:00
Brian Fopiano 935d11b433 V+ 2022-04-16 01:23:15 -07:00
cyberpwn e9eeb8335d f 2022-04-14 23:58:06 -04:00
cyberpwn 4a2f42437f Mantle deletes 2022-04-14 23:47:25 -04:00
cyberpwn 51b0693c99 fix 2022-04-14 20:43:10 -04:00
cyberpwn 185b366d8d Lots of crap 2022-04-14 20:40:37 -04:00
cyberpwn a40e533068 Revert "Drop oraxen support "
This reverts commit d315e99b63.
2022-04-14 14:32:49 -07:00
Brian Fopiano 5b3918fcb1 Merge pull request #782 from VolmitSoftware/Development
Development
2022-04-13 16:49:22 -07:00
cyberpwn d315e99b63 Drop oraxen support 2022-04-13 19:17:25 -04:00
cyberpwn 8090ba0259 Fix object placement 2022-04-13 19:11:31 -04:00
cyberpwn 584400d011 Revert "This is to prevent warnings for weird objects, (revert)"
This reverts commit 124ad23a95.
2022-04-13 18:50:08 -04:00
cyberpwn 124ad23a95 This is to prevent warnings for weird objects, (revert) 2022-04-13 18:47:31 -04:00
cyberpwn 6b59aa38ae Allow terrain to use the world height 2022-04-13 18:46:19 -04:00
Brian Fopiano 03554eb372 Merge pull request #778 from VolmitSoftware/Development
Development
2022-04-03 01:41:16 -07:00
Brian Fopiano dabfe41f29 Merge branch 'master' into Development 2022-04-03 01:40:24 -07:00
Brian Fopiano 6e2c6fc51e V+ 2022-04-03 01:36:31 -07:00
Brian Fopiano d81986bcc5 255 & no more flying objects
lol why does this happen
2022-04-03 01:34:43 -07:00
Brian Fopiano ea17e916b8 Gradle updates 2022-04-02 14:14:59 -07:00
Brian Fopiano ce56224038 Merge pull request #777 from Vatuu/Development
Fixed MythicMobs api change
2022-04-02 13:11:13 -07:00
Vatuu 9978932391 Fixed MythicMobs api change 2022-04-02 22:03:27 +02:00
Brian Fopiano 2ebc9e0deb V+
2.0.3-1.18.2
2022-03-27 18:06:00 -07:00
Brian Fopiano 7ddc621009 Merge pull request #769 from VolmitSoftware/Development
Updated PAPI Support
Temp Disabled Oraxen Support
Temp Disabled Ascync Tp
updated NMS (Colors and binds etc...)
Fixed my registry oopsie (thx Vatuu)
removed Debug Prints
/iris Height works now
Colors Fixed
2022-03-27 13:51:27 -07:00
cyberpwn aa4e574eef Fix 1.18.2 Biomes 2022-03-27 14:42:13 -04:00
Brian Fopiano 15bc964fcc Ascync tp is disabled for now
Ascync TP is done in 1.18 already, and this is just a dirty fix to stop crashing/ lol
2022-03-18 16:05:38 -07:00
Brian Fopiano 99713ac964 Need to be version specific now 2022-03-18 15:18:35 -07:00
Brian Fopiano f9a6bd1343 Readding Oraxen Link for Texting 2022-03-17 14:52:40 -07:00
Brian Fopiano f4bee872e1 Merge pull request #768 from Vatuu/Development
Fixes
2022-03-17 14:49:13 -07:00
Vatuu be99b286b3 Fixed Brian's mistake. 2022-03-17 22:43:59 +01:00
Brian Fopiano c7d5212f82 Oraxen link, and NMS WORKING, but not working
Jar loads, Color Borked, ill look at it tomorrow
2022-03-17 00:46:42 -07:00
Brian Fopiano 75726b3d34 Updating PAPI 2022-03-17 00:37:03 -07:00
Brian Fopiano 8d7e76ed81 Attempting to update NMS
(Part 1 of ...)
2022-03-17 00:36:49 -07:00
Brian Fopiano 01b08b2297 Revert "Partial For Vatuu to look at NMS"
This reverts commit cf51581ac1.
2022-03-16 19:38:17 -07:00
Brian Fopiano cf51581ac1 Partial For Vatuu to look at NMS 2022-03-16 19:31:43 -07:00
Brian Fopiano d99fd56995 Debug/height fixed/removed
Small fixes.
2022-03-15 22:18:22 -07:00
Vatuu d149051c27 Fixed wrong description for /iris height. 2022-03-14 18:22:04 +01:00
Vatuu 660aaa87ca Removed debug printout. 2022-03-14 18:20:04 +01:00
DanMB 4010f03b05 ff 2022-03-14 09:52:23 -07:00
DanMB e2d2dcf337 f 2022-03-14 09:47:57 -07:00
DanMB b11aaf3f0c Desperate times 2022-03-14 09:27:50 -07:00
DanMB 30ae065cb4 Drop 255 hardcode checks 2022-03-14 09:26:31 -07:00
Brian Fopiano 7f5b0559aa V+ 2022-03-12 15:52:14 -08:00
Brian Fopiano 851954efb2 Merge pull request #763 from VolmitSoftware/Development
Development Changes (WIP)
2022-03-12 15:50:44 -08:00
Brian Fopiano b8c6a459a6 Merge pull request #761 from Vatuu/Development
Worldheight preperations and absolute fluid height
2022-03-11 16:07:02 -08:00
Vatuu 4523089440 Added customNbt property to IrisLoot objects. 2022-03-12 01:05:28 +01:00
Vatuu 217fe47878 Made fluidHeight in dimension config refer to a static y value. 2022-03-09 23:05:33 +01:00
Vatuu 525e3db24a Working on supporting custom heights per world. 2022-03-09 22:42:20 +01:00
Brian Fopiano c3cac7d8c7 Merge pull request #756 from VolmitSoftware/Development
Development 2.0.1-1.18.X Merge
2022-03-04 09:51:00 -08:00
Brian Fopiano a92717bb1d Merge remote-tracking branch 'origin/Development' into Development 2022-03-04 09:45:40 -08:00
Brian Fopiano 9dc32eada2 V+ 2022-03-04 09:45:33 -08:00
Brian Fopiano d1644c0454 Merge pull request #757 from Vatuu/Development
Made IrisEffect dependent on world's minimum height.
2022-03-03 13:17:32 -08:00
Vatuu 81ce84b3f1 Made IrisEffect dependent on world's minimum height. 2022-03-03 22:13:15 +01:00
Brian Fopiano 18a8a405e0 Revert "Merging Stranges Trim Command to see Practicality"
This reverts commit f639c5b14e.
2022-03-03 07:53:19 -08:00
Brian Fopiano 07840b3b55 Cleanup 2022-02-22 00:15:32 -08:00
Brian Fopiano f639c5b14e Merging Stranges Trim Command to see Practicality
It seems like a neat thing to HAVE even if we wont use it
2022-02-21 23:41:13 -08:00
Brian Fopiano a6851554e8 Merge remote-tracking branch 'origin/Development' into Development 2022-02-21 23:39:52 -08:00
Brian Fopiano 6859b83fe0 Changed Dimension Height Verbose 2022-02-21 23:39:44 -08:00
Brian Fopiano 699da3cd26 Updates the Listing to support NewHorizon/End
Update listing.json
2022-02-21 23:37:07 -08:00
Brian Fopiano 6d429ef57b Temp fix for particles not showing up 2022-02-21 23:25:01 -08:00
Brian Fopiano 0bc7c20106 These region tags are unused, and unreferenced, 2022-02-21 23:24:44 -08:00
Nowhere d8f83d09ee Again an Upsi fixed 2022-02-19 10:58:53 +01:00
Nowhere 43874d35ec Fixed an Upsi 2022-02-19 10:57:18 +01:00
Nowhere 5a186e4b02 Update listing.json
Add newhorizions and theend
2022-02-19 10:56:08 +01:00
DanMB 70b3ac21ef Late AF 2022-02-16 14:48:26 -07:00
DanMB 7f24782aee Avoid white screens 2022-02-15 12:58:08 -07:00
DanMB 009835c17f Handle your engine signals 2022-02-15 12:57:56 -07:00
Brian Fopiano 18db39d480 id say this is V+ Worthy 2022-02-11 21:32:40 -08:00
Brian Fopiano 5956c358d0 Merge pull request #723 from CocoTheOwner/villagerPatch
Remove cartographer villagers due to crashes
2022-02-09 13:54:16 -08:00
Brian Fopiano f18232eb6c Merge pull request #716 from CocoTheOwner/SuppressZLIBError
Only print stacktrace when non-ZLIB exception
2022-02-09 13:52:58 -08:00
Brian Fopiano ac69d57de0 Merge pull request #726 from CocoTheOwner/dimensionDefault
"Default" IrisDimension instead of "Overworld"
2022-02-09 13:49:52 -08:00
Brian Fopiano 9297d4a7d2 Merge pull request #724 from CocoTheOwner/noUpdateRendererSpam
Do not spam renderer updates
2022-02-09 13:48:22 -08:00
Brian Fopiano e6fd4e2255 Merge pull request #722 from CocoTheOwner/fixCustomOutput
CocoLocation Fix
2022-02-09 13:47:43 -08:00
Brian Fopiano c0fa6db602 Merge pull request #735 from CocoTheOwner/noConsoleNPE
Enable edit context
2022-02-09 13:47:09 -08:00
Brian Fopiano d61d732a75 Merge pull request #736 from CocoTheOwner/customSpawnEntity
Context Fix
2022-02-09 13:46:24 -08:00
Brian Fopiano e9c0ac3c6b Merge pull request #744 from TheHappyHopper/master
Temp fix for entity spawn height (fixes #730)
2022-02-09 13:44:20 -08:00
Brian Fopiano e071ecc1d7 Merge branch 'master' into master 2022-02-09 13:43:50 -08:00
Brian Fopiano 14f6ef3da0 Merge pull request #743 from ValkrysFr/master
Hot fix mob spawning height
2022-02-09 13:41:58 -08:00
TheHappyHopper edac6d1f22 Temp fix for entity spawn height
Temporary fix for the mob spawning issue until 1.18 heights are natively added
2022-02-09 09:26:51 -08:00
LLIcocoman 525dfab3e3 Adapt getHeight complex function to negative limits 2022-02-07 10:45:16 +01:00
LLIcocoman 0b77986034 Hot fix mob spawning height 2022-02-07 10:43:09 +01:00
Brian Fopiano f0ab72db92 grammar 2022-02-03 09:17:26 -08:00
DanMB 6243555db1 Drop headless 2022-02-02 23:59:20 -07:00
Brian Fopiano abefb652c1 needed 2022-02-02 12:09:09 -08:00
CocoTheOwner 483e3ff800 Revert "Enable edit context"
This reverts commit df2c1ca6f2.
2022-01-30 16:03:36 +01:00
CocoTheOwner 0642e5a67e Revert "Enable edit context"
This reverts commit df2c1ca6f2.
2022-01-30 16:03:18 +01:00
CocoTheOwner 9553aa6e15 It's an entity, not a particle (can be cows, idk) 2022-01-30 15:45:33 +01:00
CocoTheOwner 090fb1a33e Prevent console NPE & speed up by not using catch/throw 2022-01-30 15:40:18 +01:00
CocoTheOwner c4896a1c07 Use "default" for IrisDimensions 2022-01-25 16:19:30 +01:00
CocoTheOwner 5f165d0a89 Allow default to be used for IrisDimensions 2022-01-25 16:19:21 +01:00
CocoTheOwner df2c1ca6f2 Enable edit context 2022-01-25 15:39:43 +01:00
CocoTheOwner 5e41b6c471 Do not continue generating the map when the engine is closed 2022-01-25 10:53:46 +01:00
CocoTheOwner 1d3fff08ed Do not spam renderer updates 2022-01-25 10:50:43 +01:00
CocoTheOwner 73ffcbcb41 Warning message in configurable radius 2022-01-24 13:48:57 +01:00
CocoTheOwner 510535fdd4 Remove cartographers entirely 2022-01-24 13:36:39 +01:00
CocoTheOwner 4ade79a307 Revert "Re-enable debug"
This reverts commit 8f3093ea2b.
2022-01-24 13:31:50 +01:00
CocoTheOwner 954abb974e Set custom output path correctly again 2022-01-24 13:06:46 +01:00
CocoTheOwner 8f3093ea2b Re-enable debug 2022-01-24 13:05:40 +01:00
DanMB 2ead5b3c40 Object recycler 2022-01-22 17:52:53 -05:00
CocoTheOwner 47cab96d10 Only print stacktrace when non-ZLIB exception 2022-01-22 14:13:58 +01:00
Brian Fopiano 7cfcebf0f5 Merge pull request #713 from ArMiN231/patch-1 2022-01-21 01:36:17 -08:00
Nowhere 12e310f1fe Update Readme
Deleted the reference of the branches due to not having branches anymore for past minecraft versions.
Plus fixing a typo
2022-01-21 10:10:28 +01:00
cyberpwn f28b855189 V+ 2022-01-20 06:43:32 -05:00
cyberpwn 43ed3aba41 Fix bukkit not doing its job 2022-01-20 06:43:27 -05:00
cyberpwn 1da4ee04d3 Default headless off 2022-01-20 06:43:16 -05:00
cyberpwn 17b5e4015d hmmm 2022-01-18 05:23:13 -05:00
cyberpwn 1a8b8b7232 V+ 2022-01-15 08:52:34 -05:00
cyberpwn fd2e8cfa8a Cleanup sources 2022-01-15 08:52:11 -05:00
cyberpwn ccd05c04a5 Merge remote-tracking branch 'origin/master' 2022-01-15 08:50:39 -05:00
cyberpwn e70edffaa3 Attempt to fix beetroot crap 2022-01-15 08:49:39 -05:00
Brian Fopiano 3ecaaf33b6 LavaHeight 2022-01-14 10:05:18 -08:00
cyberpwn c424ea52fa Fix a biome bug issue 2022-01-14 08:14:21 -05:00
cyberpwn ebb1aaeefd Fix data loader issues 2022-01-14 08:11:09 -05:00
cyberpwn f2d1f7bf52 Fix spawning & block updating height issues 2022-01-14 08:07:35 -05:00
Brian Fopiano deb42da38e Just adding lavaSetting, need to implement still 2022-01-13 13:44:36 -08:00
cyberpwn 43df2a754d Fix resource loader threading issues 2022-01-13 07:28:18 -05:00
cyberpwn d468d0ca28 Revert "Switch!"
This reverts commit 47fa31515d.
2022-01-13 07:21:55 -05:00
cyberpwn e586d0aecf Fixes 2022-01-13 07:21:14 -05:00
cyberpwn 8e36fed7bf Sec opts 2022-01-13 07:20:40 -05:00
cyberpwn 2e3bf585ea String garbage reduction 2022-01-13 07:19:00 -05:00
cyberpwn f57ad5a888 Number tweaks 2022-01-13 07:18:14 -05:00
cyberpwn 47fa31515d Switch! 2022-01-13 07:16:45 -05:00
cyberpwn 7e45c17078 Fixes 2022-01-13 07:16:07 -05:00
cyberpwn 32a0fc9b17 Random variable tweaks & opts 2022-01-13 07:14:16 -05:00
cyberpwn 62c2757afc Remove useless bitwise ops 2022-01-13 07:09:52 -05:00
cyberpwn 7a7596fb7b Remove unneeded supps 2022-01-13 07:05:34 -05:00
cyberpwn 708b0d6365 2022 Sources 2022-01-13 07:01:02 -05:00
cyberpwn daf1a1baa2 Cleanup 2022-01-13 06:58:05 -05:00
cyberpwn 59150a655a Fix regen 2022-01-13 06:52:37 -05:00
cyberpwn 5594ad1ce3 Root cleanup 2022-01-13 06:45:58 -05:00
cyberpwn f12bd1368f Update adventure 2022-01-13 06:35:10 -05:00
Dan 6921ad49db Merge pull request #704 from VolmitSoftware/1.18.1
1.18.1
2022-01-12 08:40:23 -05:00
cyberpwn 4430fd21f5 Cleanup sources 2022-01-12 08:39:55 -05:00
cyberpwn 64d8f22757 Ignore the setting lol not possible now 2022-01-12 08:38:34 -05:00
cyberpwn 2a73524a19 Default world hint use dimension height requests 2022-01-12 08:27:55 -05:00
cyberpwn bfd7a02c9d Iris WC use dimension heights 2022-01-12 08:27:04 -05:00
cyberpwn b108a0164a Headless use dimension height request 2022-01-12 08:26:01 -05:00
cyberpwn 9f1aa916e4 Configurable world heights 2022-01-12 08:23:58 -05:00
cyberpwn 5144a2a2df Default heights for world 2022-01-12 08:17:45 -05:00
cyberpwn c9c2deae1a Headless default heights 2022-01-12 08:17:36 -05:00
cyberpwn b220db9d85 Terrain chunk fixes 2022-01-12 08:17:28 -05:00
cyberpwn f9e3d96673 New default heights 2022-01-12 08:17:14 -05:00
cyberpwn 6c2b65055b Support negative MCA file sections 2022-01-12 08:12:19 -05:00
cyberpwn 5fa1efb1f2 Fix by id nms 2022-01-12 08:01:26 -05:00
cyberpwn 6b5d4fc557 ? 2022-01-12 07:32:59 -05:00
cyberpwn 369b125c52 Scoreboard min height patch 2022-01-12 07:23:08 -05:00
cyberpwn 732e049252 Height command check info 2022-01-12 07:18:23 -05:00
cyberpwn cdb419c3f1 Fixes 2022-01-12 06:24:26 -05:00
cyberpwn 2dde426df6 Cleanup 2022-01-12 01:47:38 -05:00
cyberpwn 8c00499e76 Height fixes 2022-01-12 01:24:12 -05:00
cyberpwn 32dac5fbe2 Fix registry access 2022-01-10 21:38:00 -05:00
cyberpwn 0d3f8de811 Fixes 2022-01-10 21:01:03 -05:00
cyberpwn 90b8747775 Fix deps 2022-01-10 19:55:51 -05:00
cyberpwn 5333d23ad8 f 2022-01-10 01:33:24 -05:00
cyberpwn e38e11b890 Fix? 2022-01-10 01:28:53 -05:00
cyberpwn 79303c90d7 Merge remote-tracking branch 'origin/1.18.1' into 1.18.1 2022-01-10 01:20:13 -05:00
cyberpwn 4eac0978ed Readme stuff 2022-01-10 01:20:10 -05:00
Brian Fopiano aef5b9a057 Fixed Typo 2022-01-09 22:14:48 -08:00
cyberpwn c65b951493 Merge remote-tracking branch 'origin/1.18.1' into 1.18.1 2022-01-10 01:01:09 -05:00
cyberpwn 75385e5c01 Fixes 2022-01-10 01:01:05 -05:00
Brian Fopiano ad19baf1f6 f 2022-01-09 21:50:05 -08:00
cyberpwn fb7acbac0a Merge remote-tracking branch 'origin/1.18.1' into 1.18.1 2022-01-10 00:48:45 -05:00
cyberpwn 7cd752ceb7 Remapped packaging 2022-01-10 00:48:42 -05:00
Brian Fopiano 9df288e7e3 Added Biomes
(Fixed them / modded some)
2022-01-09 21:38:39 -08:00
cyberpwn 26396bbe1a Update shadow 2022-01-09 23:47:30 -05:00
cyberpwn a21850e85f Fix ver 2022-01-09 23:47:01 -05:00
cyberpwn b19358914a Drop unused (broken) math helper funcs 2022-01-09 23:45:13 -05:00
cyberpwn 5a296f2d06 Update vanilla biome map (needs style) 2022-01-09 23:45:02 -05:00
cyberpwn 755c84039a NMS 1.18 2022-01-09 23:44:48 -05:00
cyberpwn 136ea44509 NMS Stuff 2022-01-09 23:20:24 -05:00
cyberpwn ba758470fc Merge remote-tracking branch 'origin/1.18.1' into 1.18.1 2022-01-09 21:06:10 -05:00
cyberpwn 878c39413a A good nms start for 1.18 2022-01-09 21:06:05 -05:00
cyberpwn a63a3a8fa5 Handle new mappings for now 2022-01-09 21:05:58 -05:00
Brian Fopiano 6a2e1f0271 Merge pull request #703 from VolmitSoftware/master
Merge
2022-01-09 16:49:35 -08:00
Brian Fopiano 21f1ecfa15 make it debug but hide error for entities 2022-01-02 16:21:59 -08:00
DanMB 9c5966c49c Im about to just throw the entire spigot jar into the cp
fuck md5
2021-12-30 20:58:43 -08:00
DanMB ffc1d01a0a Build tools exec fixes 2021-12-30 20:15:36 -08:00
DanMB e66e6e0b51 Print java version 2021-12-30 20:07:39 -08:00
DanMB b40a86e114 Update gradle 2021-12-30 20:07:30 -08:00
DanMB ef23d6d51a Test 2021-12-30 19:56:37 -08:00
Brian Fopiano 4fea28b313 This maked a moddable energy maximum, its default is the same
Imma just push it
2021-12-30 19:55:31 -08:00
Brian Fopiano 5502fa4584 just kidding, silence it 2021-12-30 18:12:44 -08:00
Brian Fopiano 0e2ae6840b Catches if an entity referenced was removed 2021-12-30 18:02:29 -08:00
Brian Fopiano 3a4493c914 V+
:3
2021-12-29 19:48:43 -08:00
Brian Fopiano 66deda7c76 Merge remote-tracking branch 'origin/master' 2021-12-29 18:32:47 -08:00
Brian Fopiano 44beeec1d7 Added OreGeneration, New generation mode 2021-12-29 18:31:56 -08:00
Brian Fopiano 824f74a4ce Fixed Deposits on chunk border
nobody even reads these
2021-12-29 18:30:36 -08:00
DanMB 8d72d2b3a4 V+ 2021-12-29 08:31:47 -08:00
Brian Fopiano 876f1231ee Merge pull request #695 from xIRoXaSx/master
Implementation of #692
2021-12-27 12:46:46 -08:00
Brian Fopiano ffbf395074 Fixes #702
Fixed the Hyphae or whatever not being good or whatever..
2021-12-26 19:20:06 -08:00
Brian Fopiano 550b513f6b Debug respects console color options 2021-12-26 19:04:53 -08:00
Brian Fopiano 3f4ee39ca9 Fixed D'Cree 2021-12-26 18:55:01 -08:00
Brian Fopiano a4887ec3c3 Merge pull request #699 from CocoTheOwner/fixDebugMsg
patch pregen debug
2021-12-26 18:17:35 -08:00
Brian Fopiano f1185d8f34 Merge pull request #700 from CocoTheOwner/allowMapVisionConsole
Allow consoles to open the vision gui
2021-12-26 18:16:58 -08:00
Brian Fopiano 030b2998eb Merge pull request #693 from CocoTheOwner/self-ref
iris settings self reference
2021-12-26 18:15:41 -08:00
Brian Fopiano 3f8ae692ff All Jigsaw bits, biomes, regions, dimensions, caves
Added
2021-12-26 17:47:08 -08:00
Brian Fopiano 07df92d75c redid focus / added coco's focus 2021-12-25 20:26:31 -08:00
Brian Fopiano 8bbf98979e Fixed Rarity map, Fixed missing blocks 2021-12-25 18:40:25 -08:00
Brian Fopiano 24408e53a3 So like... Fixed the cache maybe? 2021-12-25 13:51:46 -08:00
DanMB f8160e254b f 2021-12-25 12:42:21 -08:00
DanMB ff9b585b29 Drop api 2021-12-25 12:31:18 -08:00
DanMB 095f413147 Warn 2021-12-25 11:59:12 -08:00
DanMB 3dd1162a59 Update caffeine 2021-12-25 11:58:43 -08:00
DanMB 436225e341 Update gson 2021-12-25 11:58:12 -08:00
DanMB ca89073537 Proper versioning 2021-12-25 11:55:15 -08:00
DanMB 7663145821 Fix 2021-12-25 11:53:36 -08:00
DanMB eed75eea58 Gradle update wrapper 2021-12-25 11:48:18 -08:00
DanMB 194955770e Fix 2021-12-25 11:25:03 -08:00
Brian Fopiano 3c3820cb38 Adding my LP 2021-12-25 11:16:54 -08:00
DanMB fc25a328bf Oh dear god 2021-12-25 11:08:29 -08:00
DanMB c7e857506d Dont use max perm size 2021-12-25 11:08:20 -08:00
CocoTheOwner d343cd4f08 Allow consoles to open the vision gui 2021-11-18 15:09:41 +01:00
CocoTheOwner b4aa1b74ab patch pregen debug 2021-11-18 14:42:05 +01:00
RoXaS b043c5eade Added timeBlock and weather check for #692 2021-11-15 22:26:07 +01:00
cyberpwn c1a6ca80f5 ... Dont loop boards if no project active 2021-11-13 18:15:12 -05:00
cyberpwn a010ef0f60 V+ 2021-11-13 17:53:55 -05:00
cyberpwn 1240632dae Force Persist mobs (config option) 2021-11-13 17:53:49 -05:00
CocoTheOwner 87df6e08ca iris settings self reference 2021-11-13 17:50:41 +01:00
cyberpwn d7ad947cbb Auto stash before revert of "Improve finding by allowing minimal distance & randomization" 2021-11-12 21:50:27 -05:00
cyberpwn 434e7f75fa Stop touching this without tests. NO MORE PRS TO DECREE 2021-11-12 21:49:55 -05:00
CocoTheOwner 6f6e74e015 Revert "Improve finding by allowing minimal distance & randomization"
This reverts commit 636fddd642.
2021-11-12 21:31:50 -05:00
CocoTheOwner 24f3176b1b Revert "Fix random"
This reverts commit 107d1fc8ff.
2021-11-12 21:31:40 -05:00
cyberpwn 2d0df240f4 Revert "f"
This reverts commit def658ccc6.
2021-11-12 21:27:24 -05:00
cyberpwn def658ccc6 f 2021-11-12 21:27:03 -05:00
cyberpwn 676b35fb1c V+ 2021-11-12 16:52:13 -05:00
cyberpwn 18a1a693b5 Fix center possibly 2021-11-12 16:48:19 -05:00
cyberpwn 617a797743 Image maps working mostly 2021-11-12 16:45:44 -05:00
cyberpwn e1711b5d03 CNG Image map support 2021-11-12 15:52:50 -05:00
cyberpwn 28ef4a0b2b Image maps 2021-11-12 15:35:45 -05:00
cyberpwn 8ee17150b8 Iris Images! 2021-11-12 15:35:26 -05:00
cyberpwn b52017625d Cellularize things 2021-11-12 06:24:47 -05:00
cyberpwn 60ac9dfca2 Nope 2021-11-12 05:16:35 -05:00
cyberpwn c671735e84 Attempt fix for spigot issues 2021-11-12 05:11:44 -05:00
cyberpwn 29e5247131 I literally did nothing here 2021-11-12 05:07:01 -05:00
cyberpwn f5c533094e fmt 2021-11-12 05:00:07 -05:00
cyberpwn 06a8e08244 Fix rarity selection stream... maybe 2021-11-12 04:59:48 -05:00
cyberpwn 30eda70dda onenable stuff (coco) 2021-11-12 04:59:38 -05:00
cyberpwn 38ec4cc3ce V+ 2021-11-11 18:29:38 -05:00
cyberpwn 435271becf Fix NPE 2021-11-11 18:25:43 -05:00
cyberpwn 991bb12c80 Drop area system 2021-11-11 17:59:19 -05:00
Dan b8b643ebdf Merge pull request #663 from CocoTheOwner/onlyWarnWhenPlayer
Only warn about null engine access when the sender is a player.
2021-11-11 17:56:48 -05:00
Dan 42ad8ba1ee Merge pull request #664 from CocoTheOwner/validOnly
Better option picking for players & console
2021-11-11 17:56:38 -05:00
Dan 9f425cce4c Merge pull request #667 from CocoTheOwner/warnWhenCreate
Warn users when using /iris create
2021-11-11 17:56:06 -05:00
Dan 8be08acfa2 Merge pull request #668 from CocoTheOwner/noTaskIfDisabled
Prevent scheduling tasks while disabled
2021-11-11 17:55:53 -05:00
Dan 6422772530 Merge pull request #671 from CocoTheOwner/betterFind
Improve finding by allowing minimal distance & randomization
2021-11-11 17:54:25 -05:00
Dan a008a425ec Merge pull request #685 from CocoTheOwner/printPregenDet
Print pregen details
2021-11-11 17:52:29 -05:00
Dan 0936015d84 Merge pull request #687 from CocoTheOwner/resourceTypeForVscode
Show entity spawners in autocomplete
2021-11-11 17:52:00 -05:00
Dan 488a59df2e Merge pull request #688 from CocoTheOwner/hForHotload
shortcut h goes to hotload instead of spawner charge
2021-11-11 17:51:41 -05:00
Dan 5bf1533067 Merge pull request #689 from CocoTheOwner/gotoInStudio
Goto for Find & add to studio
2021-11-11 17:51:25 -05:00
CocoTheOwner 77024cd651 Goto for Find & add to studio
My brain still defaults to /iris studio goto so this'll help :)
2021-11-01 12:07:33 +01:00
CocoTheOwner d0e209b2eb shortcut h goes to hotload instead of spawner charge 2021-11-01 11:32:28 +01:00
CocoTheOwner d934fc181c Show entity spawners in autocomplete 2021-11-01 11:30:36 +01:00
Dan 2ca6235b5d Add files via upload 2021-10-27 19:59:34 -04:00
cyberpwn a271ed000e Revert "Revert "Graphs & streams ""
This reverts commit 4d64ad8bb8.
2021-10-26 23:40:54 -04:00
cyberpwn 4d64ad8bb8 Revert "Graphs & streams "
This reverts commit 66a717b927.
2021-10-26 23:40:52 -04:00
cyberpwn 66a717b927 Graphs & streams 2021-10-26 19:02:57 -04:00
CocoTheOwner 79114a90f5 Print pregen details 2021-10-21 09:24:50 +02:00
cyberpwn 4427748b8b Update lombok 2021-10-21 02:04:42 -04:00
StrangeOne101 ae9b8326a4 Added studio trim command
- Added studio trim command. Allows you to analyze/delete unused files in a project/pack
2021-10-15 19:13:17 +13:00
cyberpwn 619c240e62 Update deps (experimental) 2021-10-09 04:29:35 -04:00
cyberpwn 19889e7d6f Fix build 2021-10-09 04:06:59 -04:00
CocoTheOwner 107d1fc8ff Fix random 2021-10-01 13:10:07 +02:00
CocoTheOwner 636fddd642 Improve finding by allowing minimal distance & randomization 2021-10-01 12:20:00 +02:00
StrangeOne101 13532a1f0a Improved Decree Commands
- Improved parsing of inputs that aren't specified. Now, all arguments that are specified are parsed first, which allows us to assume the non specified ones more easily
- Fixes #662
2021-10-01 18:40:57 +13:00
CocoTheOwner e1ace2ba08 Prevent scheduling tasks while disabled 2021-09-29 21:42:41 +02:00
CocoTheOwner 96f0c1d148 Warn users when using /iris create that they should use multiverse instd 2021-09-29 18:11:13 +02:00
CocoTheOwner 03ecf8fe82 Better headers & warnings 2021-09-27 15:41:15 +02:00
CocoTheOwner 1c03a7fc5c 3 tries + either valid or null 2021-09-27 15:33:45 +02:00
CocoTheOwner 85fe65612e Only accept posted console option picking when valid result 2021-09-27 15:26:40 +02:00
CocoTheOwner 88469f6141 Merge branch 'master' into onlyWarnWhenPlayer 2021-09-27 15:24:23 +02:00
CocoTheOwner d81ba4a8c8 Only warn about null engine access when the sender is a player. 2021-09-27 15:23:38 +02:00
cyberpwn 734c0a37f7 V+ 2021-09-26 12:04:08 -04:00
cyberpwn 7becca450a Woop works 2021-09-26 11:17:41 -04:00
cyberpwn f2a5489363 Data bits 2021-09-26 04:42:44 -04:00
cyberpwn c7a8cb566b fix 2021-09-25 18:25:08 -04:00
cyberpwn a80031c3c5 Data fixes for tec plates 2021-09-25 18:25:00 -04:00
cyberpwn b6d9eb3dcc f 2021-09-25 14:18:30 -04:00
cyberpwn 4a594a8e4f Fix 2021-09-25 13:58:18 -04:00
cyberpwn 384a28c517 Clean up 2021-09-25 13:52:50 -04:00
cyberpwn 57ef3842e9 UCW 2021-09-25 13:51:36 -04:00
Dan 5b509466e6 Merge pull request #659 from CocoTheOwner/pickOptionForConsol,e
option picking auto-pick nr1 for consoles
2021-09-25 13:50:30 -04:00
Dan 7019947083 Merge pull request #657 from CocoTheOwner/noMultiLine
Reduce multi-line console statements
2021-09-25 13:49:27 -04:00
Dan 5ddc9243dd Merge pull request #661 from CocoTheOwner/defaultHeadless
Make headless default
2021-09-25 13:49:05 -04:00
cyberpwn 1dd89597fd Fix 2021-09-25 13:49:40 -04:00
cyberpwn 2ac1de4ae9 Reduced cache defaults 2021-09-25 13:44:09 -04:00
cyberpwn 1f783ddf44 Remap settings 2021-09-25 13:42:45 -04:00
cyberpwn bee920f96a DANGER V+ 2021-09-25 13:37:54 -04:00
cyberpwn 333e158ca5 Bust the cache 2021-09-25 12:55:27 -04:00
CocoTheOwner 67baf07efc Make headless default 2021-09-25 17:56:14 +02:00
cyberpwn 4ba8ecd3fd Fix mode fail due to pres svc 2021-09-25 08:55:19 -04:00
cyberpwn aee42cdecc Fix issues with decorants on objects fixes #660 2021-09-25 08:55:10 -04:00
cyberpwn adb2a32fda Fix index OOB for caves / mca cap fixes #655 2021-09-25 08:45:28 -04:00
cyberpwn d2c373b27d Force stop bursters if longer than 7s 2021-09-25 08:40:35 -04:00
cyberpwn b0060bcb82 More settings 2021-09-25 08:22:13 -04:00
cyberpwn 4f63db3f70 Async tp 2021-09-24 09:35:16 -04:00
cyberpwn 0d356514f2 Fix ores in air structures 2021-09-24 08:38:11 -04:00
CocoTheOwner 6ce37944a7 This is better 2021-09-24 11:00:27 +02:00
CocoTheOwner 8ef75f7c34 option picking auto-pick nr1 for consoles
Consoles cannot pick options, so we select the first option for them.
2021-09-24 09:52:18 +02:00
cyberpwn bf2da499d6 Fixes 2021-09-23 21:23:38 -04:00
CocoTheOwner ef2fe6a775 Two more 2021-09-23 17:07:40 +02:00
CocoTheOwner 2ac84f14be Just so they don't go on multiple lines :) 2021-09-23 17:06:48 +02:00
Dan 5078becbb3 Merge pull request #653 from CocoTheOwner/rawCommands
Raw commands in Entities
2021-09-23 10:57:27 -04:00
cyberpwn e19784a429 Still busted. Build at your own risk 2021-09-23 10:56:22 -04:00
cyberpwn 1628652264 BITS BITS EVERYWHERE 2021-09-23 10:44:56 -04:00
CocoTheOwner 668c7d301b Merge branch 'master' into rawCommands 2021-09-23 12:29:50 +02:00
CocoTheOwner d56a63e712 Requested patches 2021-09-23 12:23:11 +02:00
CocoTheOwner 8070b211ab Revert "OK I'm really guessing at this point"
This reverts commit 0a85521028.
2021-09-23 12:15:46 +02:00
cyberpwn 5ed59d0282 Atomic bit storage 2021-09-23 06:07:44 -04:00
cyberpwn 70223cfcd6 Atomic crude incrementalIdentityHashmaps 2021-09-23 06:01:32 -04:00
cyberpwn 7d0ff90b01 Atomic Linear Palettes 2021-09-23 05:52:59 -04:00
cyberpwn ecc09a710a Allow disabling paletted hunks 2021-09-23 05:50:38 -04:00
cyberpwn 846b4faefa Fix caves 2021-09-23 05:39:41 -04:00
cyberpwn 66da382789 Tweaks and fixes 2021-09-23 05:28:11 -04:00
cyberpwn 3378723bdd World manager fix engine close post ticks 2021-09-23 04:33:43 -04:00
cyberpwn 56723330b3 No cache locks 2021-09-23 04:32:49 -04:00
Dan 08e2244975 Merge pull request #656 from CocoTheOwner/noPregenEngineShutdown
Stop pregen on engine shutdown
2021-09-23 04:23:06 -04:00
cyberpwn f9e934fa1a Auto stash before revert of "Drop it " 2021-09-22 12:54:06 -04:00
CocoTheOwner 30eacc5019 Stop pregen on engine shutdown 2021-09-22 17:57:02 +02:00
cyberpwn 3ba7c18288 Revert "Drop it "
This reverts commit cc66e7e7c8.
2021-09-22 09:54:41 -04:00
cyberpwn 958a09b280 Update settings 2021-09-22 08:56:54 -04:00
cyberpwn cc66e7e7c8 Drop it 2021-09-22 08:55:45 -04:00
cyberpwn 7f58e0413c Not worth, 2021-09-22 08:54:38 -04:00
cyberpwn 3c256ddc5d PaletteHunkOr 2021-09-22 06:43:23 -04:00
Dan d2f8e62ecc Merge pull request #649 from CocoTheOwner/fixes
fixes
2021-09-22 06:37:51 -04:00
Dan 5ce479b1af Merge pull request #650 from CocoTheOwner/betterHotloadCMD
Better hotloading command (allows console)
2021-09-22 06:37:08 -04:00
Dan be2b893a3f Merge pull request #652 from CocoTheOwner/studioInfo
Add another update message to make sure the command is ran
2021-09-22 06:36:16 -04:00
cyberpwn 32994ce4af Merge remote-tracking branch 'origin/master' 2021-09-22 06:28:41 -04:00
cyberpwn 0c6a43fe0a Basic palette hunk 2021-09-22 06:28:37 -04:00
cyberpwn 81e0376102 Palette container support custom data 2021-09-22 06:26:57 -04:00
cyberpwn f21306a19d Linear palettes for custom data 2021-09-22 06:26:38 -04:00
cyberpwn 5f3dcac8e1 Hashmap palettes for custom data 2021-09-22 06:26:27 -04:00
cyberpwn 600bf94044 Basic impl for global palettes 2021-09-22 06:26:18 -04:00
cyberpwn 3b38b69034 Support .equals 2021-09-22 06:26:05 -04:00
cyberpwn faa8f0f9b6 A palette type for writing / reading 2021-09-22 06:25:54 -04:00
cyberpwn 125624b09e Drop useless palette classes 2021-09-22 06:25:42 -04:00
cyberpwn 7186afda93 Slice support palettes 2021-09-22 06:25:23 -04:00
cyberpwn ee542abb5d Sep palettes for other uses 2021-09-22 05:21:33 -04:00
cyberpwn c7638a6485 NBTSpecific palettes 2021-09-22 05:21:23 -04:00
CocoTheOwner 16795871c3 Run commands in effects 2021-09-21 20:44:31 +02:00
CocoTheOwner 54eff0e27f Good enough 2021-09-21 20:27:08 +02:00
CocoTheOwner 0a85521028 OK I'm really guessing at this point 2021-09-21 20:17:31 +02:00
CocoTheOwner 587af255a6 fix 2021-09-21 19:53:24 +02:00
CocoTheOwner 06326f4d55 Add another update message to make sure the command is ran 2021-09-21 19:46:09 +02:00
CocoTheOwner b73f9798e4 Introduction if IrisCommands 2021-09-21 19:45:32 +02:00
Dan 7ef5031717 Merge pull request #645 from CocoTheOwner/densityRange
Density range.
2021-09-21 12:29:26 -04:00
CocoTheOwner ba30258ee6 Better hotloading command (allows console) 2021-09-21 18:18:38 +02:00
CocoTheOwner 940a65cac0 Revert "fixes"
This reverts commit 8539bc5ade.
2021-09-21 18:17:51 +02:00
CocoTheOwner 7878b05030 Run raw commands for entity spawns 2021-09-21 17:57:55 +02:00
CocoTheOwner 16eb63d2ea last one 2021-09-21 17:56:14 +02:00
CocoTheOwner 8539bc5ade fixes 2021-09-21 17:31:58 +02:00
Dan 02f357a88d Merge pull request #643 from CocoTheOwner/fixes
Improve some commands
2021-09-21 08:30:48 -04:00
cyberpwn 92f72b45d5 V+ 2021-09-21 08:19:29 -04:00
cyberpwn 7755b19abc 3D Cache to caff 2021-09-21 08:19:24 -04:00
cyberpwn 6b62ba1c5b Mantle reduction mem 2021-09-21 08:18:51 -04:00
cyberpwn 2514aa2e3c Drop updates after use 2021-09-21 08:18:33 -04:00
cyberpwn 82982c54f9 Optional object type name placement 2021-09-21 08:18:13 -04:00
cyberpwn 726718a6f5 Mantle in pregen methods 2021-09-21 08:18:00 -04:00
cyberpwn 1609e232a2 New settings 2021-09-21 08:17:44 -04:00
cyberpwn fda2e265af clean pregener 2021-09-21 08:17:38 -04:00
cyberpwn 5c19ae0e94 f 2021-09-20 11:37:18 -04:00
CocoTheOwner f01b3caeb1 Merge branch 'master' into fixes 2021-09-20 16:40:37 +02:00
CocoTheOwner 9e7262660f Density range. Now use getDensity(rng, x, z, IrisData) 2021-09-20 16:39:19 +02:00
cyberpwn 4489197d01 V+ 2021-09-20 07:38:37 -04:00
cyberpwn 3d411fc6f3 Find strongholds closes #639 2021-09-20 07:33:18 -04:00
cyberpwn d4f26577a5 Autoconfigure iris first time startup 2021-09-20 07:14:56 -04:00
cyberpwn c744f761a1 Wire modes to engine 2021-09-20 07:14:38 -04:00
cyberpwn 7224ca43f3 Auto configuration modes 2021-09-20 07:14:31 -04:00
cyberpwn b95cea35a2 Modes 2021-09-19 03:15:49 -04:00
cyberpwn d891791929 Engine modes 2021-09-19 00:48:16 -04:00
CocoTheOwner 826b0ad4ce Improve some commands 2021-09-18 15:07:36 +02:00
cyberpwn c99c61cc7a V+ 2021-09-18 05:40:24 -04:00
cyberpwn 5eb57de264 Nobody 2021-09-18 05:29:06 -04:00
cyberpwn 976cfdbbc2 Performance improvements 2021-09-18 05:08:49 -04:00
cyberpwn a7aea8d1ed Starcast is 60+% faster 2021-09-18 04:42:23 -04:00
cyberpwn 9caa89b733 Gross 2021-09-18 04:25:26 -04:00
cyberpwn e42515c1ea Cleanup 2021-09-17 23:49:25 -04:00
cyberpwn 901169775d V+ 2021-09-15 18:46:51 -04:00
cyberpwn 3d86540d1d Remove interpolators log 2021-09-15 18:46:23 -04:00
cyberpwn 6cb97d067e Fix op spigot jig exit 2021-09-14 12:21:51 -04:00
cyberpwn ad323ebf2b Proper derefs 2021-09-14 12:17:39 -04:00
cyberpwn 18efedf7c5 Use main pool 2021-09-14 11:38:05 -04:00
cyberpwn 4eaf4ff26b Fixes 2021-09-14 11:37:59 -04:00
cyberpwn b1d2ac7822 Hella nice goto 2021-09-13 20:36:50 -04:00
cyberpwn 87e6e583a6 Locators 2021-09-13 18:40:40 -04:00
cyberpwn 2897e1a3ad Routing 2021-09-13 18:40:28 -04:00
cyberpwn dfd5a7c21d Locate objects 2021-09-13 18:40:22 -04:00
cyberpwn 093f6411c7 Locate jigsaw structures 2021-09-13 18:40:15 -04:00
cyberpwn 845bea522b Spiral util for infininity 2021-09-13 18:39:54 -04:00
cyberpwn d0175f9c39 Marker exhaustion & auto-removal in invalid positions 2021-09-13 16:46:04 -04:00
cyberpwn 0b1d59e398 Marker destruction 2021-09-13 16:33:24 -04:00
cyberpwn 93bcb8994f Height streams only 2021-09-13 10:08:41 -04:00
cyberpwn ecee464b34 Fix decorant mods 2021-09-13 09:51:33 -04:00
cyberpwn 42789fcc24 Fix vision 2021-09-13 09:47:44 -04:00
cyberpwn f26b36f3e8 Rounded height stream for mantle 2021-09-13 09:47:35 -04:00
cyberpwn 9f09052900 Fix biome mods 2021-09-13 09:47:24 -04:00
cyberpwn 95a7ba0785 No modifiers 2021-09-13 09:47:09 -04:00
cyberpwn ef4a3b405d Fix complex 2021-09-13 09:46:59 -04:00
cyberpwn 7096fcd43e No more true height streams 2021-09-13 09:46:55 -04:00
cyberpwn eeab12ed86 Only compute if absent/present if compute is not needed (locking opts) 2021-09-13 09:31:56 -04:00
cyberpwn ca961e8498 Multicore smartbore 2021-09-13 09:14:08 -04:00
cyberpwn 40b0a37110 Fixes 2021-09-12 12:08:35 -04:00
cyberpwn 6daf0861c9 Marker spawning 2021-09-12 12:02:36 -04:00
cyberpwn 03bc1e722e Optimal updates only 2021-09-12 11:12:48 -04:00
cyberpwn ce94c5e237 Dont wait for the mantle to load, skip it until it's loaded (async) 2021-09-12 10:56:58 -04:00
cyberpwn 88aad1e107 Reduce cave updates 2021-09-12 10:53:56 -04:00
cyberpwn c4fe14bd45 Reduced updates 2021-09-12 10:42:36 -04:00
cyberpwn 2191b539e3 Dont warn on markers 2021-09-12 09:28:41 -04:00
cyberpwn dcd3495564 Clean 2021-09-12 09:10:18 -04:00
cyberpwn 30559841d5 Integrate markers with world mgr (initial spawns only so far) 2021-09-12 09:06:19 -04:00
cyberpwn 0dc0d5c981 Markers 2021-09-12 08:37:57 -04:00
cyberpwn 3b981e2818 V+ (UNSTABLE DO NOT USE) 2021-09-12 08:28:47 -04:00
Dan 47bfc74ba9 Delete gradlebuild.yml 2021-09-12 08:26:09 -04:00
cyberpwn 8858e1bce8 Cleanup 2021-09-12 08:26:09 -04:00
cyberpwn 04be430b8c Fix mtl carving streams 2021-09-12 08:24:51 -04:00
cyberpwn d46351b866 Remove feature streams from worldmgr 2021-09-12 08:24:19 -04:00
cyberpwn d3ec29aa42 Drop features from project compression 2021-09-12 08:23:21 -04:00
cyberpwn 357abee462 Vision update streams 2021-09-12 08:22:40 -04:00
cyberpwn c86a040768 Update render streams 2021-09-12 08:22:26 -04:00
cyberpwn d9e7dc158d Drop engine stream types 2021-09-12 08:22:11 -04:00
cyberpwn 46591968c0 Drop feature command on studio 2021-09-12 08:21:54 -04:00
cyberpwn bb7c256ecf Remove features from mtl object chances 2021-09-12 08:21:36 -04:00
cyberpwn d239ed1c9f Jig component remove feature defs 2021-09-12 08:20:49 -04:00
cyberpwn 604dece366 Remove mantle component: feature 2021-09-12 08:20:02 -04:00
cyberpwn 8b55f20684 Drop feature size radius computation in mantle engine 2021-09-12 08:18:46 -04:00
cyberpwn 8bf12ff32c No features in board service 2021-09-12 08:18:11 -04:00
cyberpwn 0596482a8d Remove features from regions 2021-09-12 08:17:46 -04:00
cyberpwn b5a77aa62a Remove Zone matter from jig structure 2021-09-12 08:17:32 -04:00
cyberpwn 5db27d9d93 Remove ZM from mantle chunk 2021-09-12 08:17:17 -04:00
cyberpwn a630e4937b Drop zone matter 2021-09-12 08:16:58 -04:00
cyberpwn 96d27d17ce Remove features from mantle chunk format 2021-09-12 08:16:38 -04:00
cyberpwn 742b848f42 Remove features from mantle writer 2021-09-12 08:16:01 -04:00
cyberpwn 57884e3400 Remove feature def from mantle 2021-09-12 08:15:42 -04:00
cyberpwn 40459f4e01 Tear out of complex features 2021-09-12 08:15:08 -04:00
cyberpwn a461072113 Remove biome registry features 2021-09-12 08:12:32 -04:00
cyberpwn 0105931414 Remove dim features registry 2021-09-12 08:12:13 -04:00
cyberpwn c0b7b871ba Remove feature command on what 2021-09-12 08:11:47 -04:00
cyberpwn 1250c94600 Drop feature classes 2021-09-12 08:11:29 -04:00
cyberpwn ccbd5b17d3 Mantle dont use post handler 2021-09-12 08:10:29 -04:00
cyberpwn 2fd3abbb14 Remove base noise features 2021-09-12 08:08:18 -04:00
cyberpwn 5bc019d3d3 Improvements 2021-09-12 08:02:41 -04:00
cyberpwn 91c24d5501 Fixes lombok 2021-09-12 07:48:01 -04:00
cyberpwn cd7f192fba Improve biome generator performance when interpolators match each other 2021-09-12 07:46:24 -04:00
cyberpwn 7d0555fb67 Cleanup 2021-09-11 13:18:58 -04:00
Dan 3864708cf1 Merge pull request #633 from VolmitSoftware/matter
Basic matter functions
2021-09-11 13:16:54 -04:00
cyberpwn ef22a58631 Matter inject 2021-09-11 13:16:57 -04:00
cyberpwn acf4fa106f Filtering for matter 2021-09-11 13:05:23 -04:00
cyberpwn 5a5704ee0b Read matter from IOB 2021-09-11 12:18:25 -04:00
cyberpwn 7e65416f10 Register matter loaders 2021-09-11 12:06:23 -04:00
cyberpwn 2d7bc59db4 Matter loader 2021-09-11 12:04:48 -04:00
cyberpwn 930469a006 Matter placer 2021-09-11 12:00:04 -04:00
cyberpwn c657ba52ad V+ 2021-09-11 07:53:35 -04:00
Dan 46499512eb Merge pull request #627 from CocoTheOwner/maxOnPregen
Max on pregen radius parameter
2021-09-11 07:51:31 -04:00
Dan c2779570fa Merge pull request #628 from CocoTheOwner/reopenVSCodeOnFail
Try opening VSCode again after first time failed
2021-09-11 07:50:37 -04:00
Dan 750dc609bb Merge pull request #630 from CocoTheOwner/vDecreeCmd
Print default string instead of full DecreeParam instance
2021-09-11 07:49:30 -04:00
cyberpwn 1037183f4c Fixes 2021-09-11 07:49:13 -04:00
cyberpwn 979f959291 Fix hunk oob through have mod 2021-09-11 06:22:57 -04:00
CocoTheOwner 86cfe0465c Print default string instead of full DecreeParam instance 2021-09-11 10:50:41 +02:00
CocoTheOwner 61d461eea5 Try opening VSCode again after first time failed 2021-09-11 09:15:45 +02:00
CocoTheOwner ff1733225a Max on pregen radius parameter 2021-09-11 09:15:09 +02:00
cyberpwn 784fca98c8 Revert "Drop features "
This reverts commit 1f0ebd8ba1.
2021-09-10 21:37:47 -04:00
cyberpwn 1f0ebd8ba1 Drop features 2021-09-10 21:33:07 -04:00
cyberpwn 2e645f04e7 Fix entities stuck in the ground closes #607 2021-09-10 09:27:11 -04:00
cyberpwn debe8eb81c Remove config v 2021-09-10 09:15:29 -04:00
Dan 2e8f8f258d Merge pull request #622 from CocoTheOwner/unusedSettings
Simplify settings
2021-09-10 09:13:03 -04:00
Dan 697f40595e Merge pull request #618 from CocoTheOwner/pregenFix
Engine is null again dan!
2021-09-10 09:12:47 -04:00
cyberpwn 37404b6619 Merge remote-tracking branch 'origin/master' 2021-09-10 09:13:10 -04:00
cyberpwn 4b3a016d70 Init engine on world touch / access / checks 2021-09-10 09:13:06 -04:00
Dan ed2af5f9b4 Merge pull request #620 from CocoTheOwner/patchDownload
Patch download
2021-09-10 09:09:13 -04:00
Dan 5418ab52b7 Merge pull request #621 from CocoTheOwner/studioDownload
Add download to studio
2021-09-10 09:07:06 -04:00
CocoTheOwner 61037af138 Remove version yeet 2021-09-10 15:06:04 +02:00
Dan f86cd11d74 Merge pull request #624 from CocoTheOwner/patches
Patches
2021-09-10 09:03:30 -04:00
CocoTheOwner e46b335dc0 ok 2021-09-10 15:02:56 +02:00
cyberpwn 79299112eb Fixes 2021-09-10 09:01:04 -04:00
cyberpwn 3d5bee3a2c Basic API 2021-09-10 08:34:02 -04:00
CocoTheOwner 466cc84c16 Patches 2021-09-10 13:55:32 +02:00
CocoTheOwner 27f9e2ea9f V+ 2021-09-10 02:14:40 +02:00
CocoTheOwner 593e10eec2 Rename mismatching version number file to another numba 2021-09-10 02:13:57 +02:00
CocoTheOwner 5d5b8b78d4 Remove little-used shortcuts, check config version 2021-09-10 02:09:58 +02:00
CocoTheOwner 620c4087bc Studio in studio tho 2021-09-10 01:36:43 +02:00
CocoTheOwner c5bf0e0cb7 Simplify settings 2021-09-10 01:35:26 +02:00
CocoTheOwner 7f7d53e00a Add download to studio 2021-09-10 01:08:30 +02:00
CocoTheOwner 073466bd6a Patch download 2021-09-09 23:20:54 +02:00
CocoTheOwner 151e88046d Engine is null again dan! 2021-09-09 14:54:29 +02:00
cyberpwn c3b1d6735e Use direct types 2021-09-09 08:35:41 -04:00
cyberpwn e3a5da64a5 Handle registered blocks 2021-09-09 08:28:22 -04:00
cyberpwn 3c4599423d Registry fixes 2021-09-09 08:28:12 -04:00
Dan 2bb1f27e07 Merge pull request #616 from CocoTheOwner/fixTreesIsh
I guess
2021-09-09 08:22:47 -04:00
Dan e3adbbbd0b Merge pull request #599 from CocoTheOwner/Fixes
aliases, startup NPE, pick option when console
2021-09-09 08:22:05 -04:00
cyberpwn 5e6838bdc9 Registries 2021-09-09 07:47:42 -04:00
cyberpwn fc6720e090 API 2021-09-09 07:00:12 -04:00
CocoTheOwner e29f671363 I guess 2021-09-08 18:06:57 +02:00
CocoTheOwner 6e501c7b8a Print seed 2021-09-08 15:15:04 +02:00
CocoTheOwner 7dd3cc6f9f Merge remote-tracking branch 'upstream/master' into Fixes 2021-09-08 15:02:56 +02:00
cyberpwn f346f080ee Revert "Revert "Revert "Revert "Revert "Revert "V+""""""
This reverts commit 9f19e585db.
2021-09-08 08:52:49 -04:00
cyberpwn 9f19e585db Revert "Revert "Revert "Revert "Revert "V+"""""
This reverts commit bc74487301.
2021-09-08 08:52:45 -04:00
cyberpwn bc74487301 Revert "Revert "Revert "Revert "V+""""
This reverts commit 693baa8cef.
2021-09-08 08:52:43 -04:00
cyberpwn 693baa8cef Revert "Revert "Revert "V+"""
This reverts commit 9ae60d4859.
2021-09-08 08:52:40 -04:00
cyberpwn 9ae60d4859 Revert "Revert "V+""
This reverts commit 281efef207.
2021-09-08 08:52:37 -04:00
cyberpwn 281efef207 Revert "V+"
This reverts commit 986de265fe.
2021-09-08 08:52:33 -04:00
CocoTheOwner d893537ae6 Merge remote-tracking branch 'upstream/master' into Fixes 2021-09-08 14:52:01 +02:00
cyberpwn 986de265fe V+ 2021-09-08 08:49:34 -04:00
cyberpwn d25633e213 Cleanup 2021-09-08 08:46:25 -04:00
cyberpwn 0c8c7157f6 Fix minimessage 2021-09-08 08:41:15 -04:00
cyberpwn e393442df9 Late engine start fix issues 2021-09-08 08:41:07 -04:00
cyberpwn fc94fe30d0 Fixes for pieces 2021-09-08 08:40:55 -04:00
cyberpwn 1065079be6 Fix spawn radius issues 2021-09-08 08:40:48 -04:00
cyberpwn e426172b18 Perfection modifiers 2021-09-08 08:40:36 -04:00
cyberpwn effe93286a Fix block data issues 2021-09-08 08:39:21 -04:00
cyberpwn 2a669618a2 Oof 2021-09-08 08:39:15 -04:00
CocoTheOwner 44cd0e1e10 Patch weird Kayori bug 2021-09-08 14:33:59 +02:00
cyberpwn 621155f06f Attempt to deal with adventure api failing now... until it gets fixed. 2021-09-08 06:34:34 -04:00
cyberpwn 6c116e261d Update adventure api 2021-09-08 06:07:41 -04:00
cyberpwn bcc89d91f7 New stage system use dep for decorants 2021-09-08 05:58:16 -04:00
cyberpwn 8e874afbd5 Bodies 2021-09-08 05:58:05 -04:00
cyberpwn 9421cd0b2d Engine tweaks for multi-stage ease of use 2021-09-08 05:55:06 -04:00
cyberpwn 37c46042c3 Engine stages 2021-09-08 05:49:40 -04:00
CocoTheOwner c5429f0515 Dum 2021-09-07 22:16:52 +02:00
CocoTheOwner f0375ca446 Better ig 2021-09-07 17:51:08 +02:00
CocoTheOwner b34bac9b5e Remove hot garbage 2021-09-07 17:44:03 +02:00
CocoTheOwner f42e418346 Better context yes 2021-09-07 17:43:13 +02:00
CocoTheOwner a060c01f58 Merge remote-tracking branch 'upstream/master' into Fixes 2021-09-07 17:41:41 +02:00
cyberpwn f41686fab4 V+ 2021-09-07 03:27:05 -04:00
cyberpwn be88d584cf More carve checking 2021-09-07 00:35:13 -04:00
cyberpwn 80aa87935d Fix seed drift 2021-09-07 00:27:48 -04:00
cyberpwn 405073cca9 V+ 2021-09-05 22:23:04 -04:00
Dan 22cb831138 Update bug.yml 2021-09-05 22:03:43 -04:00
cyberpwn 95c8c75789 Fix deep slate ore issues 2021-09-05 21:57:32 -04:00
cyberpwn e8e3cadeff Revert "Stop alias spamming"
This reverts commit 892d3be144.
2021-09-05 21:53:57 -04:00
CocoTheOwner 8123ba9a01 Merge branch 'master' into Fixes 2021-09-05 11:39:21 +02:00
cyberpwn 5aa6c98dc1 V+ 2021-09-04 15:00:17 -04:00
cyberpwn 8e16b8e4a8 Cleanup 2021-09-04 14:59:56 -04:00
cyberpwn 90f0a5eac6 Structure & Piece handlers for jigsaw decree 2021-09-04 14:58:11 -04:00
cyberpwn 892d3be144 Stop alias spamming 2021-09-04 14:55:28 -04:00
cyberpwn 164242ae4b Reimplement jigsaw closes #610 2021-09-04 14:50:35 -04:00
cyberpwn 07b8c5087b Re-implement regen closes #611 2021-09-04 14:39:17 -04:00
cyberpwn abba2e969b Fluid bodies disable for now 2021-09-04 14:25:23 -04:00
cyberpwn 0a4b57ea01 Lake gen 2021-09-04 14:25:15 -04:00
cyberpwn 422ecb758f Elipsoid functions 2021-09-04 14:25:09 -04:00
cyberpwn 20f953b9cb Fix imports post merge 2021-09-04 14:06:27 -04:00
cyberpwn d2f1488ef1 Merge remote-tracking branch 'origin/master' 2021-09-04 14:05:52 -04:00
cyberpwn a3aedd5fad Connect configuration with the mantle for fluid bodies 2021-09-04 14:05:16 -04:00
cyberpwn e2d07f7e78 Fluid body configuration for lakes & rivers 2021-09-04 14:00:45 -04:00
cyberpwn 3aa0fac852 Fluid body configuration 2021-09-04 13:55:59 -04:00
cyberpwn 238243a7d4 Fluid body components for mantle 2021-09-04 13:54:40 -04:00
cyberpwn 9533b62ebf Check carving sizes 2021-09-04 13:54:19 -04:00
cyberpwn d06445e04b Default worms 2021-09-04 13:54:09 -04:00
DanLT 69255a938b f 2021-09-03 17:39:50 -08:00
DanLT 773f24238f Fix decorant cave issues fixes #586 2021-09-03 11:56:50 -08:00
DanLT 548ed4a1f0 Cache nodes single core 2021-09-03 11:47:09 -08:00
DanLT 76bf764262 Fix deepslate ores in zones 2021-09-03 11:39:40 -08:00
DanLT 075882b7b0 Fix initial exceptions 2021-09-03 11:34:09 -08:00
DanLT d25299e1ba Stop walls from placing above surface fixes #590 2021-09-03 11:29:44 -08:00
CocoTheOwner 0160d42302 aliases, startup NPE, pick option when console 2021-09-03 21:28:12 +02:00
DanLT 6ca7a013d6 Merge remote-tracking branch 'origin/master' 2021-09-03 11:23:03 -08:00
DanLT 60ed93e13b Convert ores to deepslate / nondeepslate depending on src fixes #593 2021-09-03 11:22:59 -08:00
DanLT 747e2e2e27 V+ 2021-09-03 11:20:45 -08:00
Dan 16d1694a78 Merge pull request #592 from CocoTheOwner/noLocateBiome
Cancel locate
2021-09-03 06:52:02 -08:00
CocoTheOwner 17e74d9ed2 Cancel locate 2021-09-02 22:31:10 +02:00
DanLT 923a42e9a3 Stop hotloading production worlds 2021-09-02 11:59:12 -08:00
DanLT bac66dfbf9 Fix issues with ores spawning problems 2021-09-02 11:54:06 -08:00
DanLT 6895c0b392 V+ 2021-09-01 15:22:31 -08:00
DanLT ef08319670 Disable cave lava for now 2021-09-01 15:17:00 -08:00
DanLT 1aee758f87 Fix center stronghold 2021-09-01 13:52:07 -08:00
DanLT 3d0eca9432 Fix anchoring point on lava carving 2021-09-01 13:50:14 -08:00
DanLT 93936a7498 Lava impl 2021-09-01 13:26:30 -08:00
DanLT 8e873ccd00 Cleanup 2021-09-01 13:09:45 -08:00
DanLT 7a188af002 Fixes 2021-09-01 13:07:49 -08:00
DanLT aaffeb9fac Studio world checker 2021-09-01 13:07:27 -08:00
DanLT 67711bcb0d Fix eb & biome context 2021-09-01 13:05:36 -08:00
DanLT f93c158caa Fix board service 2021-09-01 13:05:18 -08:00
DanLT c74d05a47b Revert "Revert "f""
This reverts commit f867c0f8a1.
2021-09-01 12:53:39 -08:00
DanLT f867c0f8a1 Revert "f"
This reverts commit 98d024295e.
2021-09-01 12:53:27 -08:00
DanLT 98d024295e f 2021-09-01 12:53:19 -08:00
DanLT 963d3de709 Support mantle cave biome getter 2021-09-01 12:33:53 -08:00
DanLT e20fa7f3a2 Update angle maxs for ravines 2021-09-01 12:28:33 -08:00
DanLT 719d262a00 Merge remote-tracking branch 'origin/master' 2021-09-01 12:21:24 -08:00
DanLT 9ad9f28f33 Fix 2021-09-01 12:21:20 -08:00
DanLT 6ac4a405b5 Noooope 2021-09-01 10:46:17 -08:00
DanLT 64fbdff13d Iris seed 2021-09-01 10:46:08 -08:00
DanLT 65011f42da Merge remote-tracking branch 'origin/master' 2021-09-01 10:27:57 -08:00
DanLT d763967d4b Cleanup 2021-09-01 10:27:49 -08:00
DanLT 44c807aedd Interpolator as registry 2021-09-01 10:27:41 -08:00
Dan 6e6bb72245 Merge pull request #591 from CocoTheOwner/fixWeird
Fix weird
2021-09-01 06:28:12 -08:00
CocoTheOwner ca96b45637 Fix weird 2021-09-01 16:27:42 +02:00
Dan d8ff7786df Merge pull request #574 from Skizzles/master
Update bug.yml
2021-09-01 06:16:08 -08:00
Dan 35ed07e0ba Merge pull request #588 from ArMiN231/patch-1
Getting rid of the double n when hotloading
2021-09-01 06:15:47 -08:00
ArMiN231 7b002606bf Update Iris.java 2021-09-01 14:24:44 +02:00
ArMiN231 ac0d0a6680 Getting rid of the double n when hotloading 2021-09-01 14:17:14 +02:00
DanLT e5033cceec Update gradle 2021-09-01 02:26:14 -08:00
cyberpwn b5d7e2969f V+ 2021-08-31 17:14:11 -04:00
cyberpwn 6b4bd90bfa f 2021-08-31 11:42:56 -04:00
cyberpwn b2aff55160 Cave spawning & markers 2021-08-31 11:24:48 -04:00
cyberpwn 569c34bca0 Fix expiring commands 2021-08-31 10:17:25 -04:00
cyberpwn e63c966756 Fix carving from digging bedrock 2021-08-31 10:13:51 -04:00
cyberpwn 43f9efb9e4 Fix commands 2021-08-31 10:12:23 -04:00
cyberpwn 82666d62ef Fixes 2021-08-30 12:25:56 -04:00
cyberpwn 72b62481a7 Console help better 2021-08-30 12:06:20 -04:00
cyberpwn 9cf13e320a Allow disabling console / ingame formatting 2021-08-30 11:55:22 -04:00
cyberpwn c8c4a0f61b fixhl 2021-08-30 08:37:19 -04:00
cyberpwn 801c44389e Schema hell 2021-08-30 08:09:48 -04:00
cyberpwn fbdb060e10 Gently fail on new overworlds 2021-08-30 07:05:53 -04:00
cyberpwn 09edc78bdc Fixes ? 2021-08-29 23:30:15 -04:00
cyberpwn 6951817088 Skip invalid field types? 2021-08-29 23:05:55 -04:00
cyberpwn 2a22d25975 Working snippets (without autocomplete) 2021-08-29 22:53:37 -04:00
cyberpwn 875adc8bdf Snippet annotation tags 2021-08-29 22:36:34 -04:00
cyberpwn b8b914a99c Re-explode the objects 2021-08-29 22:25:16 -04:00
cyberpwn 59401d27ca Snippets! 2021-08-29 22:19:41 -04:00
cyberpwn 26cb21f541 Precache decree help 2021-08-29 22:19:35 -04:00
cyberpwn 49154759c0 Fix j 2021-08-29 12:43:02 -04:00
cyberpwn abc07968e5 V+ 2021-08-29 10:26:48 -04:00
cyberpwn d28899ae84 f 2021-08-29 10:26:23 -04:00
cyberpwn 04c44de145 Fix infinite spawning bug 2021-08-29 10:25:52 -04:00
cyberpwn 7997d52df5 Follow command sounds setting 2021-08-29 09:39:00 -04:00
cyberpwn f3d899cbf5 *Better* Underwater Caves 2021-08-29 09:34:03 -04:00
cyberpwn e540596c20 Underwater caves 2021-08-29 09:07:38 -04:00
cyberpwn b1aa7a8e88 Less verbose 2021-08-29 08:12:30 -04:00
cyberpwn 3eef5b2c02 Fix loader close bug 2021-08-29 08:06:16 -04:00
cyberpwn 443aa0040c Better warnings 2021-08-29 07:51:00 -04:00
cyberpwn 48c1db6a8a Optimized dust 2021-08-29 07:25:07 -04:00
cyberpwn b9f2530e2f Why? 2021-08-28 22:54:30 -04:00
cyberpwn 3cb7c0206a CMd sound 2021-08-28 22:52:42 -04:00
cyberpwn a7360f1145 Glitter type 2021-08-28 22:42:58 -04:00
cyberpwn c4e3c4b34f Suggest always unless its a category 2021-08-28 22:42:53 -04:00
cyberpwn ae763ae25d Command pagination 2021-08-28 22:34:05 -04:00
cyberpwn dd0b4f27d4 Cleanup 2021-08-28 21:50:50 -04:00
cyberpwn faf5bcb32b Activate decree 2021-08-28 21:48:15 -04:00
cyberpwn 93f2c83c0e Fix what command 2021-08-28 21:43:39 -04:00
cyberpwn 63b5545502 Move what to iris 2021-08-28 21:32:02 -04:00
cyberpwn 31c99517d7 Revert "Revert "Fixes ""
This reverts commit f3a2be4a06.
2021-08-28 21:29:48 -04:00
Dan 2e3a2ff69a Revert "Revert "Merge pull request #561 from CocoTheOwner/DecreeCommands""
This reverts commit a451189d83.
2021-08-28 21:29:42 -04:00
cyberpwn 4aa2c83e03 Support dripstone tip_merge 2021-08-28 19:56:54 -04:00
cyberpwn 376d7a8878 Fixes 2021-08-28 19:54:41 -04:00
cyberpwn dd5f55f4fc Biome ceiling floor wall & decorator support for caves 2021-08-28 19:54:31 -04:00
cyberpwn d2806c7ec0 Dripstone support 2021-08-28 19:54:16 -04:00
cyberpwn 2db34f4d49 Auto stash before revert of "Merge pull request #561 from CocoTheOwner/DecreeCommands" 2021-08-28 16:26:45 -04:00
Dan a451189d83 Revert "Merge pull request #561 from CocoTheOwner/DecreeCommands"
This reverts commit d24b9ec0ae.
2021-08-28 16:26:40 -04:00
cyberpwn f3a2be4a06 Revert "Fixes "
This reverts commit 8e53be6810.
2021-08-28 16:26:35 -04:00
cyberpwn dddd97bc22 Test 2021-08-28 12:39:04 -04:00
cyberpwn 8e53be6810 Fixes 2021-08-28 12:34:50 -04:00
Dan d24b9ec0ae Merge pull request #561 from CocoTheOwner/DecreeCommands
Object commands WIP
2021-08-28 08:26:39 -08:00
CocoTheOwner d8900341ac remove what 2021-08-28 18:01:24 +02:00
CocoTheOwner 0465d047e7 Update Commands with patches 2021-08-28 17:59:56 +02:00
CocoTheOwner cf1f3677ae Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-28 17:59:40 +02:00
cyberpwn 354b02612d Forking 2021-08-28 11:55:47 -04:00
cyberpwn f06d5b806a Ravines! 2021-08-28 11:13:13 -04:00
CocoTheOwner 49385a08ad Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-28 16:19:29 +02:00
CocoTheOwner d07c8c8851 What & debug fix 2021-08-28 16:18:53 +02:00
cyberpwn 803c3678e3 Updates & fixes 2021-08-28 09:52:42 -04:00
cyberpwn 3118f743a0 Fixes 2021-08-27 11:29:18 -04:00
cyberpwn 2fa5c7eca4 Carving slice 2021-08-27 10:24:13 -04:00
cyberpwn a09829ed4b Updates on their own slice 2021-08-27 10:24:01 -04:00
cyberpwn cf3d92d6e1 CAVES AND MORE 2021-08-27 09:53:25 -04:00
cyberpwn 524b63e88c Caves in the mantle 2021-08-27 05:32:40 -04:00
cyberpwn 0a930db7d6 Flag carved for carving components 2021-08-27 05:09:21 -04:00
cyberpwn e6338bbde1 Add carving config to 3 masters 2021-08-27 05:09:13 -04:00
cyberpwn e4f7dd21b6 Mantle carving component 2021-08-27 05:09:06 -04:00
cyberpwn b0bae93d06 Carving package 2021-08-27 05:08:58 -04:00
cyberpwn 9b93a5cd04 Move editing stuff to writer 2021-08-27 05:08:40 -04:00
cyberpwn 3142717154 More removals 2021-08-27 04:57:14 -04:00
cyberpwn 3dcfacfeed Remove Caves, Carving & Ravines 2021-08-27 04:48:00 -04:00
cyberpwn a51f5fefc4 Remove spot & ridge biomes 2021-08-27 04:40:53 -04:00
cyberpwn fcc3bbf9b6 Remove lakes & shores 2021-08-27 04:40:06 -04:00
cyberpwn 109390d21e V+ 2021-08-27 02:46:48 -04:00
cyberpwn c7dd91ef90 Update gson 2021-08-27 02:46:45 -04:00
cyberpwn 3d15abfdae Debugging 2021-08-27 02:45:49 -04:00
cyberpwn a9caaefd16 More fixes 2021-08-27 02:43:27 -04:00
cyberpwn 261c786a10 Fix vacuum 2021-08-27 02:32:57 -04:00
cyberpwn a5270ccea8 Fixes 2021-08-27 01:35:23 -04:00
cyberpwn 08865dc218 Blank packs 2021-08-27 01:11:13 -04:00
cyberpwn 3f50767e48 New pack api 2021-08-27 00:47:38 -04:00
cyberpwn 8ff5887955 Auto stash before revert of "Cleanup" 2021-08-27 00:16:30 -04:00
cyberpwn 2aa240337c Cleanup 2021-08-26 06:35:12 -04:00
cyberpwn 44d75d9955 Cleanup 2021-08-26 06:12:32 -04:00
cyberpwn 3b61b87501 Fix 2021-08-26 06:07:39 -04:00
cyberpwn 04de362bc9 Rise 2021-08-26 05:39:41 -04:00
cyberpwn e06175f600 Buffet mode fixes 2021-08-26 03:08:21 -04:00
cyberpwn fe78d4f1e7 Fix regens 2021-08-26 02:02:25 -04:00
cyberpwn 4a1de4c0da Chunk regeneration 2021-08-26 01:46:56 -04:00
CocoTheOwner 22b77411ec Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-25 21:12:12 +02:00
cyberpwn 3f730ead84 V+ 2021-08-25 08:42:46 -04:00
cyberpwn d52d70a014 Woo fixes 2021-08-25 08:35:47 -04:00
cyberpwn 0a7b485514 Biomes in MCA 2021-08-25 07:59:16 -04:00
CocoTheOwner 151c384313 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-25 13:30:01 +02:00
cyberpwn 8586d44d7e Integrate real 1.17 chunks 2021-08-25 07:10:31 -04:00
cyberpwn 5c8b600cb1 QPos & utils 2021-08-25 07:10:15 -04:00
cyberpwn afd67d2f00 Biome storage format 2021-08-25 07:10:07 -04:00
cyberpwn 125a3fdc5e Rename pkg 2021-08-25 07:09:57 -04:00
CocoTheOwner 9963ff9c8e Auto stash before merge of "DecreeCommands" and "upstream/master" 2021-08-25 13:08:54 +02:00
CocoTheOwner 8547ea4c6a Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-25 13:08:45 +02:00
cyberpwn fd434e8b21 Remove old palettes 2021-08-25 06:34:03 -04:00
cyberpwn 1524866432 HELP ME 2021-08-25 06:32:54 -04:00
cyberpwn 19fa9390ff EVEN MORE PALETTES 2021-08-25 06:32:50 -04:00
cyberpwn a335050332 Fixes 2021-08-25 06:32:43 -04:00
cyberpwn 8aa9ecffc5 Fixes 2021-08-25 05:18:16 -04:00
cyberpwn ef02e5169d More cleanup 2021-08-25 05:18:13 -04:00
cyberpwn ba31a56949 Allow get value on tags 2021-08-25 05:18:07 -04:00
cyberpwn 6b7bd75b18 DONT USE IDENTITY! 2021-08-25 03:56:00 -04:00
cyberpwn 013d6625d5 More fixes 2021-08-25 03:55:54 -04:00
cyberpwn ca228ca23e Fix palettes 2021-08-25 03:55:47 -04:00
cyberpwn 1dce68212f No generics in the palette. 2021-08-25 03:55:39 -04:00
cyberpwn 2683c2433b Nasty suppliers 2021-08-25 03:55:25 -04:00
cyberpwn 2929800a9f Global palette theft 2021-08-25 02:43:00 -04:00
cyberpwn c1b04ace78 Fix atomic cache issues 2021-08-25 02:42:50 -04:00
cyberpwn 04a54dbc2a Integrate DP with sections 2021-08-25 02:42:42 -04:00
cyberpwn e7c1f31315 Drop chunk support for 1.13 and below 2021-08-25 02:42:26 -04:00
cyberpwn 62f6917d81 Regid fixes 2021-08-25 02:42:01 -04:00
cyberpwn babefc7bd3 Linear deobf 2021-08-25 02:41:55 -04:00
cyberpwn a09c9f4a2e DataPalette Hash fixes 2021-08-25 02:41:50 -04:00
cyberpwn 5876598ca0 DP Global 2021-08-25 02:41:42 -04:00
cyberpwn f45d643739 Deobf datapalette block & fix a ton of issues 2021-08-25 02:41:37 -04:00
cyberpwn c1ba176e27 Deobf dbits 2021-08-25 02:41:24 -04:00
cyberpwn 9b3013c51a Deobf datapalette 2021-08-25 02:41:13 -04:00
cyberpwn a746720a6e Update math helper 2021-08-25 02:40:51 -04:00
cyberpwn a95c61c1d8 DP Tests 2021-08-25 02:40:42 -04:00
cyberpwn d5da8e4e2b Forcefully integrate a mangled DataPalette from NMS into a NBTMCA API 2021-08-24 23:16:01 -04:00
cyberpwn 861e11a713 Data Palette stuff for 1.17 mca 2021-08-24 22:37:55 -04:00
cyberpwn 23ac209c05 f 2021-08-24 22:28:39 -04:00
cyberpwn 8df789ae34 Cleanup 2021-08-24 22:28:33 -04:00
cyberpwn 432e95e4ec Commons lang3 2021-08-24 22:26:29 -04:00
cyberpwn 401ed0a7a5 Remove hunk writers & old nbt 2021-08-24 22:26:22 -04:00
CocoTheOwner dae6957093 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-24 18:52:27 +02:00
cyberpwn 7b6405fba7 Prepatch 2021-08-24 08:24:36 -04:00
cyberpwn 6dc3e74607 Atomic NBT List 2021-08-24 04:09:26 -04:00
cyberpwn 617066340b Remove block data caching 2021-08-24 04:00:17 -04:00
CocoTheOwner 2dd6db352f Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-23 13:21:27 +02:00
cyberpwn 56e13641df V+ 2021-08-23 03:15:17 -04:00
cyberpwn 9d62113388 Performance Improvements 2021-08-23 03:12:17 -04:00
Skizzles 47b4a6c498 Update bug.yml
fix my stupid spelling
2021-08-22 04:30:21 -07:00
Skizzles 7dfe3a1c59 Update bug.yml
Fixes support links not linking to discord
2021-08-22 04:29:42 -07:00
cyberpwn 44acfc7ec8 Fix surfaces underwater 2021-08-22 04:22:16 -04:00
cyberpwn 7933f5e357 Fix height placement on structures 2021-08-22 03:55:39 -04:00
cyberpwn 858c5a27ad Sync post 2021-08-22 03:49:51 -04:00
cyberpwn 28e41a0ae9 Fix B 2021-08-22 03:49:41 -04:00
cyberpwn ae79f9be66 Fix noise explorer 2021-08-22 03:45:48 -04:00
cyberpwn 4a6d58c36e Locky overrides 2021-08-22 03:30:39 -04:00
cyberpwn 5bf6687f1f Compiling 2021-08-22 03:04:30 -04:00
cyberpwn beb80f0422 Modernize B 2021-08-22 03:04:24 -04:00
cyberpwn 0e86d6fbdb Libs 2021-08-22 03:03:57 -04:00
cyberpwn 2a8e4f19af Stop closing production worlds 2021-08-21 21:50:12 -04:00
cyberpwn 6ed8b6d058 Fix 2021-08-21 21:46:01 -04:00
cyberpwn 33812b3f4a Fixes 2021-08-21 21:41:37 -04:00
cyberpwn b62e542801 "S" 2021-08-21 09:42:49 -04:00
cyberpwn 0080110fad Auto stash before revert of "f" 2021-08-21 09:31:15 -04:00
cyberpwn 19ef661457 Revert "f"
This reverts commit 7946221f0b.
2021-08-21 07:33:52 -04:00
cyberpwn 7946221f0b f 2021-08-21 07:33:45 -04:00
cyberpwn 203a42c043 Engine fixes 2021-08-21 06:21:15 -04:00
cyberpwn d3998dd03b Config tweaks 2021-08-21 05:56:15 -04:00
cyberpwn e752a48343 Drop decree from yml for now 2021-08-21 02:52:11 -04:00
cyberpwn 71955b777b Unreg decree for now 2021-08-21 02:52:00 -04:00
cyberpwn d686d07d53 Iris but forkjoin 2021-08-21 02:42:22 -04:00
cyberpwn 72b4c9c6ab Revert "Fix mantle "
This reverts commit 59399124fa.
2021-08-21 00:31:52 -04:00
cyberpwn 4b991ab67a f 2021-08-21 00:29:05 -04:00
cyberpwn 59399124fa Fix mantle 2021-08-21 00:29:01 -04:00
cyberpwn 3eb4962d7d f 2021-08-21 00:25:27 -04:00
cyberpwn 42d3b4c326 Auto stash before revert of "Test" 2021-08-20 01:34:24 -04:00
cyberpwn f3931567f9 Revert "Test"
This reverts commit 154adafbcb.
2021-08-20 01:34:20 -04:00
cyberpwn 154adafbcb Test 2021-08-20 01:34:14 -04:00
cyberpwn 4a1e511262 Fixes 2021-08-19 22:39:36 -04:00
cyberpwn 3fc907e50f Cave placers 2021-08-19 22:39:04 -04:00
cyberpwn bbf441c3b4 Worms 2021-08-19 22:26:40 -04:00
cyberpwn bbf660d80e Caves 2021-08-19 22:26:31 -04:00
cyberpwn a1c3bc26c9 Worms 2021-08-19 16:36:54 -04:00
cyberpwn fc73e0e482 Advanced mantle editing 2021-08-19 16:36:45 -04:00
Dan e7727afbe8 Update gradlebuild.yml 2021-08-19 07:06:35 -04:00
Dan de5ce2660d Create gradlebuild.yml 2021-08-19 07:06:10 -04:00
CocoTheOwner 2e661cb2f8 Patch object handlers 2021-08-19 12:37:30 +02:00
cyberpwn ebeaad0b4e Worm iterators 2021-08-19 06:27:38 -04:00
CocoTheOwner ce60c048f8 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-19 12:22:08 +02:00
cyberpwn 6795ff7db9 Compile at least 2021-08-19 06:21:37 -04:00
cyberpwn 931f1fb27d Why itj why. 2021-08-19 06:21:18 -04:00
CocoTheOwner 74625a8d65 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-19 12:21:08 +02:00
cyberpwn 03e8ccee7d Worms 2021-08-19 06:21:03 -04:00
CocoTheOwner 01b9e4cb55 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-19 12:20:47 +02:00
cyberpwn b5d5014ba2 Fix decree for objects 2021-08-19 06:20:43 -04:00
CocoTheOwner 4f7bfd14c0 alias O in object because studiO was taking priority 2021-08-19 12:02:30 +02:00
CocoTheOwner 7347300f05 Rework undo for Decree
Undo now in a service.
No longer per-player, collective history
We're not worledit - Dan
Removes the need for deprecated offlinePlayer calls as well.
2021-08-19 11:57:56 +02:00
cyberpwn aa6bdd2786 Mantle height causing chunk holes 2021-08-19 05:42:52 -04:00
CocoTheOwner db0aeeabbd Shift 2021-08-19 11:28:30 +02:00
cyberpwn 9aa426ed56 Remove vertical domains for now 2021-08-19 04:36:58 -04:00
CocoTheOwner 30f9348c26 doc 2021-08-19 09:56:35 +02:00
CocoTheOwner baf76c51f9 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-19 09:54:55 +02:00
CocoTheOwner 6abef569e8 Further simplifications of sender for clarity 2021-08-19 09:54:51 +02:00
CocoTheOwner ade88a6055 rename fileName + alias force 2021-08-19 09:54:33 +02:00
CocoTheOwner 11d0274a1b Simple null check 2021-08-19 09:54:21 +02:00
cyberpwn 957de5d6f7 Cleanup terrain actuator 2021-08-19 03:38:07 -04:00
cyberpwn f5f4c5c672 Performance improvements 2021-08-19 03:26:45 -04:00
cyberpwn 5924495035 Fix sea surface 2021-08-18 23:41:51 -04:00
cyberpwn c486f65e74 Revert "Legacy objects copy"
This reverts commit 7f7397f684.
2021-08-18 23:26:49 -04:00
cyberpwn b19a7d45d8 Unfuck the decorator stacks 2021-08-18 23:26:30 -04:00
cyberpwn 7f7397f684 Legacy objects copy 2021-08-18 17:23:54 -04:00
cyberpwn 39142909f6 V+ 2021-08-18 15:03:59 -04:00
cyberpwn 23f5b84c5e No spam 2021-08-18 15:03:34 -04:00
cyberpwn 630b6e6313 MVC Fixes 2021-08-18 14:58:23 -04:00
cyberpwn 4396ca9420 Light level for spawns closes #558 2021-08-18 14:24:03 -04:00
cyberpwn 5286a69c34 More shutdown fixes 2021-08-18 14:08:43 -04:00
cyberpwn 059d5542c9 Post shutdown fixes 2021-08-18 14:08:28 -04:00
cyberpwn a09e77a17d Fix odd color negative -> ? 2021-08-18 14:08:13 -04:00
CocoTheOwner 5a236e3bea Fuck that builder. Yeet 2021-08-18 15:05:47 +02:00
CocoTheOwner aba5add7f9 Engine check 2021-08-18 15:05:31 +02:00
CocoTheOwner a4190672d3 Don't sort values again (done in getParams) 2021-08-18 13:14:53 +02:00
CocoTheOwner 10da9182d1 Improve boolean handling in DecIris 2021-08-18 13:14:41 +02:00
CocoTheOwner 2de5bc2855 Dont sort subvalues to allow better control 2021-08-18 13:14:29 +02:00
CocoTheOwner b32db945be patch debug 2021-08-18 13:05:19 +02:00
CocoTheOwner 44d02e6dfd Aliases, colors & patches 2021-08-18 12:58:59 +02:00
CocoTheOwner fc096dc6fa See description
- No-parameter command nodes are now on-click-runnable
- Parameters are now sorted by: name *and then* required
2021-08-18 12:58:45 +02:00
CocoTheOwner 1d52bbde58 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-18 12:03:08 +02:00
cyberpwn 1a049e854b Tests 2021-08-18 04:37:52 -04:00
CocoTheOwner 5b15ef81f5 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-18 10:09:29 +02:00
CocoTheOwner 05088c1063 Matter & save 2021-08-18 10:09:25 +02:00
cyberpwn 04c09efd75 Fix WM 2021-08-18 03:54:08 -04:00
cyberpwn 2c0576099a Patch MVC Stupidity 2021-08-18 03:54:01 -04:00
cyberpwn 27b49f9d7e ??? 2021-08-18 03:16:18 -04:00
cyberpwn 6a247c425a More debug 2021-08-18 03:13:33 -04:00
cyberpwn 7910034916 World installs 2021-08-18 03:09:03 -04:00
cyberpwn c56e5d17be Use loadkeys 2021-08-18 03:02:17 -04:00
cyberpwn 522eabce5e debug spawns 2021-08-18 02:58:30 -04:00
cyberpwn 0e4b874994 Support initial spawns 2021-08-18 02:49:12 -04:00
cyberpwn 319cc8a5f9 () 2021-08-18 01:45:50 -04:00
cyberpwn b5c26392e1 V+ 2021-08-18 01:26:04 -04:00
cyberpwn 7d53c55cc1 Remove diag 2021-08-18 01:24:37 -04:00
cyberpwn 3e30994541 Patch rotation glitch 2021-08-18 01:24:17 -04:00
cyberpwn a669fcba7a Remove structure test spawning of entities 2021-08-18 00:54:23 -04:00
cyberpwn 851053fd9a Allow onion bursting for post mantle jobs 2021-08-18 00:39:16 -04:00
cyberpwn b71a87e803 Allow y locking for structures 2021-08-18 00:39:03 -04:00
cyberpwn c72658d21a PD 2021-08-17 23:51:41 -04:00
cyberpwn d0b8c59769 No RTE on closing a closed mantle 2021-08-17 23:46:25 -04:00
cyberpwn 583b74fb7e No leaky 2021-08-17 23:40:47 -04:00
cyberpwn cf6f28aa3e Fixes 2021-08-17 20:31:30 -04:00
cyberpwn f3ece64635 Platform with exclusive for hotloading & closing 2021-08-17 18:42:10 -04:00
cyberpwn 6343b7087d ffs 2021-08-17 17:26:38 -04:00
cyberpwn e279dc4cea A less garbage hotloader 2021-08-17 16:46:13 -04:00
cyberpwn fc5763c8e8 Skip for profiling now 2021-08-17 16:09:58 -04:00
cyberpwn 9e32fcda88 Experimental support for vertical warping 2021-08-17 16:08:01 -04:00
cyberpwn 7fe8c7b547 Drop locate 2021-08-17 16:07:51 -04:00
cyberpwn 38329514f9 Fixes 2021-08-17 15:57:53 -04:00
cyberpwn 37705dfc71 Fix hotloading 2021-08-17 14:21:50 -04:00
cyberpwn e8e7e3144f Check if shaped gen is flat 2021-08-17 14:21:43 -04:00
cyberpwn 0da229e8f4 Remove under feature 2021-08-17 12:29:19 -04:00
cyberpwn 1029b2523d Vertical domain 2021-08-17 12:27:25 -04:00
cyberpwn 477b520cf0 Fix IData 2021-08-17 12:24:54 -04:00
Dan d54fdf5452 Merge pull request #557 from CocoTheOwner/DecreeCommands
Small fixes
2021-08-17 08:23:08 -08:00
CocoTheOwner bfb47aba87 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-17 18:17:17 +02:00
CocoTheOwner d241a57d38 Object 2021-08-17 18:17:13 +02:00
CocoTheOwner 01479a82de More merge conflicts please 2021-08-17 18:07:38 +02:00
CocoTheOwner 198820d95d PlaySound in volmitsender 2021-08-17 18:04:09 +02:00
cyberpwn 86741a4e95 Cache changes 2021-08-17 11:28:41 -04:00
CocoTheOwner 7d859661ba Sorry for merge conflicts :) 2021-08-17 17:22:27 +02:00
CocoTheOwner d04c95514e Studio patches 2021-08-17 16:55:33 +02:00
CocoTheOwner e6e3953a85 Auto stash before merge of "DecreeCommands" and "upstream/master" 2021-08-17 16:17:40 +02:00
CocoTheOwner 104f1232f3 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-17 16:16:49 +02:00
StrangeOne101 b55bf6e6c1 2 Bug Fixes
- Fixed #552 by making sure the biome saved in datapacks is all lowercase
- Fixed extra character showing when Iris downloads a pack
2021-08-17 23:20:09 +12:00
cyberpwn d79af8fcc2 Fix update 2021-08-17 00:19:12 -04:00
cyberpwn ff05429f58 Attempt 2021-08-16 23:51:40 -04:00
cyberpwn 0dbabe629a More debug 2021-08-16 23:47:33 -04:00
cyberpwn 3348d46b5a Endersignal debug 2021-08-16 23:41:07 -04:00
cyberpwn aff1a245c2 Make hotloading idiot proof 2021-08-16 23:30:33 -04:00
cyberpwn 51802f71a5 Fix hotloading bricking the engine 2021-08-16 23:23:39 -04:00
cyberpwn 32f34f1444 Dim installing 2021-08-16 19:35:03 -04:00
cyberpwn b7309ccdf1 Comments 2021-08-16 19:15:31 -04:00
cyberpwn 8abaac56ff Fix dim creation 2021-08-16 19:13:06 -04:00
cyberpwn a462ab98e9 Cleanup sources 2021-08-16 18:53:01 -04:00
cyberpwn af602a414a Managers -> Services 2021-08-16 18:51:53 -04:00
CocoTheOwner 67e426354c Small fixes 2021-08-17 00:35:25 +02:00
Dan e2f8e31fa0 Merge pull request #556 from CocoTheOwner/DecreeCommands
More decree
2021-08-16 13:55:55 -08:00
cyberpwn d67cce4184 v 2021-08-16 17:55:54 -04:00
CocoTheOwner ca63b412cb Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-16 23:54:14 +02:00
CocoTheOwner 89e372c229 More info for decree 2021-08-16 23:54:11 +02:00
cyberpwn 4ae5b49764 Service 2021-08-16 17:53:55 -04:00
cyberpwn 1057f32b34 Tweaks 2021-08-16 17:50:50 -04:00
CocoTheOwner e3948eb4ba bunch of defaults 2021-08-16 23:47:29 +02:00
CocoTheOwner 6d363235a5 oops 2 2021-08-16 23:17:51 +02:00
CocoTheOwner fb50b4fa78 oops 2021-08-16 23:17:41 +02:00
CocoTheOwner 85fbaa2b97 Iris What 2021-08-16 23:17:17 +02:00
cyberpwn 1440453640 Cancer 2021-08-16 17:15:38 -04:00
cyberpwn e364959d25 Download utils 2021-08-16 16:53:03 -04:00
cyberpwn 89642e71b5 Fix param 2021-08-16 16:23:41 -04:00
Dan d8413b3b7d Merge pull request #554 from CocoTheOwner/DecreeCommands
Remove aliases that *should* be interpretable by the processor
2021-08-16 12:18:33 -08:00
CocoTheOwner 92ba038af7 Remove name = where not needed 2021-08-16 22:17:54 +02:00
CocoTheOwner 2ac2cc2ebf Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-16 22:15:58 +02:00
cyberpwn 990ed86283 V+ 2021-08-16 16:10:37 -04:00
cyberpwn 953d702ed6 Allow leaf decay if setting is changed 2021-08-16 16:10:04 -04:00
CocoTheOwner 1fa828ad1a Move files add what patches 2021-08-16 22:08:43 +02:00
cyberpwn 9f16fa189f Compiler tweak for param names 2021-08-16 16:07:00 -04:00
cyberpwn a7e78cc3fb Num util 2021-08-16 16:01:01 -04:00
cyberpwn 353c40cc46 Update number handlers 2021-08-16 16:00:05 -04:00
CocoTheOwner 3a7c8d660c DecIrisWhat 2021-08-16 21:51:17 +02:00
CocoTheOwner b2c9108ffc Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-16 21:50:14 +02:00
cyberpwn ef447e2ba1 Dec settings (later) 2021-08-16 15:49:07 -04:00
cyberpwn 85f0b37b03 Fix format handler for bad locale 2021-08-16 15:48:58 -04:00
cyberpwn 9f6cdfcd11 Vector handlers 2021-08-16 15:48:44 -04:00
CocoTheOwner 376477767b no instance 2021-08-16 21:46:39 +02:00
CocoTheOwner f1376be2e0 No mow wegen 2021-08-16 21:42:12 +02:00
CocoTheOwner 5e57ec0e67 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-16 21:38:01 +02:00
CocoTheOwner 15721f1279 Pregen & create 2021-08-16 21:37:53 +02:00
Dan e8fe58438d Merge pull request #555 from CocoTheOwner/fixEntitySpawn
Spawn entity using block instead of tile since it was causing issues
2021-08-16 11:17:59 -08:00
Dan 8ef04b1ad0 Merge pull request #553 from CocoTheOwner/fixContextHandlers
Move context handlers to separate folder because of jarscanner issue
2021-08-16 11:16:29 -08:00
CocoTheOwner a3d6776c02 Merge pack overwrite with download (under param) 2021-08-16 20:44:42 +02:00
CocoTheOwner e7a529257a Metrics 2021-08-16 20:38:46 +02:00
CocoTheOwner 714afabf30 Download 2021-08-16 19:04:07 +02:00
CocoTheOwner f1df8d07bb Spawn entity using block instead of tile since it was causing issues
https://pastebin.com/gwXGK8qd
2021-08-16 18:50:14 +02:00
CocoTheOwner a18c4b6449 Studio charge (no longer in debug) 2021-08-16 18:48:42 +02:00
CocoTheOwner 2099342352 debug 2021-08-16 18:30:53 +02:00
CocoTheOwner 50ee2727a2 bitwise 2021-08-16 18:23:13 +02:00
CocoTheOwner 571e918ee0 aura 2021-08-16 18:13:57 +02:00
CocoTheOwner 7c5d8721c0 Studio command patches & generator context
Generator context is pretty poorly implemented
2021-08-16 13:17:14 +02:00
CocoTheOwner b5984a965e Merge branch 'fixContextHandlers' into DecreeCommands 2021-08-16 12:20:57 +02:00
CocoTheOwner 0e0bd94251 Remove aliases that *should* be interpretable by the processor 2021-08-16 12:06:04 +02:00
CocoTheOwner a9d2d52c1e Move context handlers to separate folder because of jarscanner issue 2021-08-16 11:57:34 +02:00
DanLT b7c67a16d9 Useful 2021-08-15 15:59:45 -08:00
DanLT 35615526a1 Even faster 2021-08-15 15:57:37 -08:00
DanLT c43ce52e78 Fixes? 2021-08-15 15:51:26 -08:00
DanLT 61a313f24b Context handlers 2021-08-15 15:12:08 -08:00
Dan d71393cead Merge pull request #536 from CocoTheOwner/DecreeCommands
Some commands (in studio mainly) WIP
2021-08-15 14:41:12 -08:00
CocoTheOwner 64bb6714a3 Ten - Remove beautify in favour of clean 2021-08-15 22:49:28 +02:00
CocoTheOwner f8336d6adb Nine - remove aliases 2021-08-15 22:46:22 +02:00
CocoTheOwner 30245f4aed Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-15 22:44:53 +02:00
Dan a861652d6f Merge pull request #546 from CocoTheOwner/fixMergeIssue
HOTFIX. Fixes the massive compile issue
2021-08-15 12:28:19 -08:00
CocoTheOwner 5fe5ba78da Eight - remove technical debt 2021-08-15 22:12:51 +02:00
CocoTheOwner 8fa9977617 Seven - getloadkey 2021-08-15 22:04:14 +02:00
CocoTheOwner 0df2dd3ce2 Six - true is not false. bad 2021-08-15 21:53:28 +02:00
CocoTheOwner 80893f2592 Five - Remove player check 2021-08-15 21:50:34 +02:00
CocoTheOwner 91cf10541b Four - remove decreeExtension 2021-08-15 21:49:03 +02:00
CocoTheOwner aaccfdee44 Three 2021-08-15 21:47:50 +02:00
CocoTheOwner a8449de731 Two - remove test 2021-08-15 21:45:54 +02:00
CocoTheOwner 9d98641a94 One - remove dupe function 2021-08-15 21:45:34 +02:00
CocoTheOwner 4f13227d9e Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-15 21:43:08 +02:00
CocoTheOwner 70e9ee0dd1 debug 2021-08-15 21:41:40 +02:00
Dan d43932a9d2 Merge pull request #545 from CocoTheOwner/noGradientNullSender
Remove the gradient if an error occurs with sender.send
2021-08-15 06:39:32 -08:00
Dan 7118261fee Merge pull request #547 from CocoTheOwner/noSelfDistruct
This doesnt get used yeet
2021-08-15 06:34:33 -08:00
CocoTheOwner 89b171db36 sync loot opener since InvOpenEv must be sync 2021-08-15 13:48:23 +02:00
CocoTheOwner 1c4778790a scolon 2021-08-15 13:36:06 +02:00
CocoTheOwner d20d1488de /ir s Find error 2021-08-15 13:35:36 +02:00
CocoTheOwner c969f277ee Add new handlers (Which don't work for no reason)
I'm getting stuck help plz. All commands that require any IrisRegistrant, including the dimensions (of which I did not modify the handler), give an invocationtargetexception
2021-08-15 00:33:52 +02:00
CocoTheOwner cf4a77af75 This doesnt get used yeet 2021-08-14 23:42:51 +02:00
CocoTheOwner 4042592420 So it's the same as the biome loader :D 2021-08-14 23:33:31 +02:00
CocoTheOwner a750241ec3 Merge remote-tracking branch 'origin/hotfix' into DecreeCommands 2021-08-14 23:26:37 +02:00
CocoTheOwner 8ab79ec1ae DAMNIT 2021-08-14 23:25:37 +02:00
CocoTheOwner a9289df93f another I hate this 2021-08-14 23:25:15 +02:00
CocoTheOwner f6c42c4b53 Idk what I did here but it's bad 2021-08-14 23:23:49 +02:00
CocoTheOwner bfb121938a Remove the gradient if an error occurs with sender.send
Otherwise, the raw gradient text is printed to the console, which is bad.
2021-08-14 23:22:53 +02:00
CocoTheOwner 20d26d4bcd getLoadKey instead of getName 2021-08-14 22:50:38 +02:00
CocoTheOwner 474b851275 loadkey instead of name 2021-08-14 22:31:39 +02:00
CocoTheOwner 5c541275c5 override equals function for hashing
lombok was complaining
2021-08-14 22:28:31 +02:00
CocoTheOwner 486e92dfc9 another I hate this 2021-08-14 22:25:14 +02:00
CocoTheOwner 959ce3bc92 Idk what I did here but it's bad 2021-08-14 22:23:51 +02:00
CocoTheOwner 3866c740e9 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-14 22:08:00 +02:00
Dan a65a85fb67 Merge pull request #538 from CocoTheOwner/noSystemOut 2021-08-14 13:11:12 -04:00
Dan c921b7bb1c Merge pull request #539 from CocoTheOwner/DecreeFixes 2021-08-14 13:07:18 -04:00
Dan 7acf2d80d2 Merge pull request #540 from CocoTheOwner/DimNameWhenNull 2021-08-14 13:05:58 -04:00
Dan 01b2d6cb39 Merge pull request #541 from CocoTheOwner/CoreBoardManager 2021-08-14 13:05:20 -04:00
CocoTheOwner e2665e3cbe Merge remote-tracking branch 'upstream/master' into noSystemOut 2021-08-14 16:33:08 +02:00
CocoTheOwner 9b95094207 Merge remote-tracking branch 'upstream/master' into DecreeFixes 2021-08-14 16:33:00 +02:00
CocoTheOwner 59b637c317 Merge remote-tracking branch 'upstream/master' into DimNameWhenNull 2021-08-14 16:32:53 +02:00
CocoTheOwner 52963e8e80 Merge remote-tracking branch 'upstream/master' into CoreBoardManager 2021-08-14 16:32:42 +02:00
CocoTheOwner 43f42ac917 Merge remote-tracking branch 'upstream/master' into DecreeCommands 2021-08-14 16:32:28 +02:00
CocoTheOwner 0352951d8c ir s Update 2021-08-14 16:25:45 +02:00
cyberpwn fe334d0aed f 2021-08-14 10:11:00 -04:00
CocoTheOwner f2371d8735 Entity spawn 2021-08-14 16:07:23 +02:00
cyberpwn 3c9608efbd Cleanup command merged 2021-08-14 10:00:43 -04:00
CocoTheOwner 8dff3b55c9 fix pack file getting from dimension & add profiler 2021-08-14 16:00:20 +02:00
CocoTheOwner b42b6303ae I skipped loot. Oops 2021-08-14 15:51:09 +02:00
CocoTheOwner 263e6d5cca lowercase matching 2021-08-14 15:48:33 +02:00
CocoTheOwner 5b5622b4a7 Polish 2021-08-14 15:35:42 +02:00
CocoTheOwner e328bf616f Utility function extension 2021-08-14 15:34:34 +02:00
cyberpwn 52ba831b1c CMD Fixes 2021-08-14 08:33:56 -04:00
CocoTheOwner 12b1016d66 Package + function 2021-08-14 12:14:47 +02:00
CocoTheOwner a471bfec6a BooleanHandler 2021-08-14 12:14:36 +02:00
CocoTheOwner 3b6d3116bd Small patch (Forgot to launch) + simplify + compact + docs + ir s map 2021-08-14 12:01:03 +02:00
CocoTheOwner 5c0c680f56 simplify studio checks + ir s loot 2021-08-14 11:53:24 +02:00
CocoTheOwner 3af8f4772f Simplify command usage + hotload 2021-08-14 11:42:19 +02:00
CocoTheOwner 10b2593389 Find + addition to DecreeExecutor
Adds world() to the decree executor
2021-08-14 11:29:24 +02:00
CocoTheOwner cf67d58236 Noise explorer 2021-08-14 11:20:32 +02:00
CocoTheOwner 748227319c Noise explorer 2021-08-14 11:14:17 +02:00
CocoTheOwner 94c18e835a CoreBoardManager 2021-08-14 11:03:50 +02:00
CocoTheOwner cfb5693ffe smol patch 2021-08-14 11:03:04 +02:00
CocoTheOwner 3e1f3d5a51 Fixes 2021-08-14 10:57:42 +02:00
CocoTheOwner c0bf529a6b ok 2021-08-14 09:34:26 +02:00
cyberpwn f951ee1f91 Formatting & fixes 2021-08-13 20:36:12 -04:00
CocoTheOwner e8ac971f9e Script. Please check this. This is probably wrong. 2021-08-13 22:57:02 +02:00
CocoTheOwner b1278aff2e editbiome 2021-08-13 22:48:14 +02:00
CocoTheOwner c26c14227c convert 2021-08-13 22:43:48 +02:00
CocoTheOwner ce36bcc951 JSON Cleaner in util & Decree tag & beatify DecreeCMD 2021-08-13 22:41:25 +02:00
CocoTheOwner 1134cd7788 Notify user of opening studio 2021-08-13 22:22:16 +02:00
CocoTheOwner 7920d2225c version 2021-08-13 22:22:07 +02:00
CocoTheOwner d0faaf2d53 desc + bootify 2021-08-13 21:58:11 +02:00
CocoTheOwner fc60878c25 why 2021-08-13 21:58:02 +02:00
CocoTheOwner 1b941536d0 Required = !hasDefault && constant 2021-08-13 21:32:41 +02:00
CocoTheOwner 4215bfc6fe CMD -> Dec + fixes 2021-08-13 21:22:22 +02:00
CocoTheOwner 22d215b2c7 Doc 2021-08-13 21:18:15 +02:00
CocoTheOwner 69eb7979c1 oops 2021-08-13 20:20:57 +02:00
CocoTheOwner 0f0c24d31b Remove system.out's 2021-08-13 20:12:35 +02:00
cyberpwn 30c5a0d9cd CHATTY 2021-08-13 13:21:46 -04:00
cyberpwn 93e0773758 Loaders 2021-08-13 11:33:12 -04:00
cyberpwn c92e64afbd Refine decree 2021-08-13 11:15:09 -04:00
cyberpwn 6ba9dc74d8 Decree system 2021-08-13 10:39:40 -04:00
cyberpwn f69c244080 New command manager 2021-08-13 10:39:35 -04:00
cyberpwn f1b4a973a8 Fix import issues in gradle 2021-08-13 10:39:23 -04:00
Dan efe49ee4c1 Merge pull request #528 from CocoTheOwner/decree+2
More docs more framework, glhf!
2021-08-13 08:25:19 -04:00
CocoTheOwner 51573cb9f5 More docs more framework, glhf! 2021-08-13 14:24:52 +02:00
cyberpwn f6ab374718 Fixes 2021-08-13 08:24:50 -04:00
Dan 7ace88ae0d Merge pull request #527 from CocoTheOwner/decree+
Document, simplify & further
2021-08-13 06:08:48 -04:00
CocoTheOwner 5f5e04506c Merge remote-tracking branch 'upstream/master' into decree+ 2021-08-13 11:59:34 +02:00
cyberpwn 9a58e121e8 Fix example 2021-08-13 05:58:44 -04:00
cyberpwn 7153347d67 Auto stash before merge of "master" and "origin/master" 2021-08-13 05:56:52 -04:00
StrangeOne101 611ccd9605 Only hotload noise gen if a generator is open 2021-08-13 21:28:23 +12:00
CocoTheOwner 0befa2eb39 target > elementtype 2021-08-13 11:06:44 +02:00
CocoTheOwner b0677bb66d Revert "Move decreeSystem into decreeCommand"
This reverts commit 3d68caed6b.
2021-08-13 10:28:54 +02:00
CocoTheOwner f476f1447c Docs 2021-08-13 10:13:59 +02:00
CocoTheOwner c8907232da patch doc 2021-08-12 23:41:27 +02:00
CocoTheOwner 864ca81d1d VolmitSender no longer saved, class never instantiated (rem unsd impt) 2021-08-12 23:38:27 +02:00
CocoTheOwner 636427ac6b Exception annotation 2021-08-12 23:37:17 +02:00
CocoTheOwner 3d68caed6b Move decreeSystem into decreeCommand
Why the extra class?
2021-08-12 23:35:11 +02:00
CocoTheOwner 4f7e25038c util function 2021-08-12 23:33:08 +02:00
CocoTheOwner ba0a95ed26 Pff + docs 2021-08-12 23:31:02 +02:00
CocoTheOwner 0cd25be408 Instantiate decreeNode with Decree 2021-08-12 23:30:02 +02:00
CocoTheOwner c6fa0ddde3 pff 2021-08-12 23:26:46 +02:00
CocoTheOwner b64a9b7a44 Instantiate DecreeParameter with @param stored + exception when not decl 2021-08-12 23:25:12 +02:00
CocoTheOwner 326bf39bed Refactor 2021-08-12 23:19:24 +02:00
CocoTheOwner ac4c37ba48 Extends 2021-08-12 23:17:26 +02:00
CocoTheOwner 305017d523 Annotate @Param & @Decree & update example 2021-08-12 23:16:51 +02:00
CocoTheOwner 19d1c552fe Remove unused imports 2021-08-12 23:16:04 +02:00
CocoTheOwner 7d6908d146 Simplify and document 2021-08-12 22:54:04 +02:00
Daniel Mills 9cf399f956 RECOVERY 2021-08-12 03:15:53 -04:00
Daniel Mills a0b4450e5b Fix command issues with studio 2021-08-10 10:16:33 -04:00
Daniel Mills 9d983a7c96 ""Stable"" 2021-08-10 09:32:32 -04:00
Daniel Mills 89be08bc58 Fix per second metric on boards 2021-08-10 09:23:21 -04:00
Daniel Mills 6dcf3e15da Unsafe but somehow safe black magic 2021-08-10 09:19:52 -04:00
Daniel Mills e66cb9479f Remove tiles 2021-08-10 08:47:23 -04:00
Daniel Mills bd8d9cc3aa Fix world create commands & pregnes 2021-08-10 08:46:27 -04:00
Daniel Mills 9453b86907 Dont configure on demand 2021-08-10 07:28:50 -04:00
Daniel Mills ecf9441fae V++ 2021-08-10 07:28:45 -04:00
Daniel Mills 70b659acbe Cleanup 2021-08-10 07:26:41 -04:00
Daniel Mills 2ebb9f2763 V+ 2021-08-10 07:26:35 -04:00
Dan 1231b13dfd Merge pull request #518 from CocoTheOwner/irisCreateRework
Rework /iris create & patch /iris pregen start
2021-08-10 07:21:38 -04:00
Dan bebad4d361 Merge pull request #521 from CocoTheOwner/patchExecute
Patch execute command
2021-08-10 07:21:19 -04:00
Dan ed5a1887f4 Merge pull request #522 from CocoTheOwner/gradle
Easier to yoink the build.gradle
2021-08-10 07:21:00 -04:00
Dan 342e689825 Merge pull request #519 from VolmitSoftware/the-mantle
The Mantle
2021-08-10 07:19:38 -04:00
Daniel Mills c5c1a9b25b Fixes 2021-08-10 07:19:17 -04:00
Daniel Mills 7637905de2 Safe saving / trimming 2021-08-10 07:19:10 -04:00
Daniel Mills ec780b7b93 Fixes 2021-08-10 07:18:59 -04:00
Daniel Mills 3728c59f1d Async trimming 2021-08-10 07:18:55 -04:00
Daniel Mills 4442d9a706 Fix gens 2021-08-10 06:07:08 -04:00
Daniel Mills 8928b35f30 Mantle tweaks 2021-08-10 06:07:02 -04:00
Daniel Mills fca189ee4c Component writers merged 2021-08-10 06:06:55 -04:00
Daniel Mills d0fb3dde66 Locks 2021-08-10 06:06:39 -04:00
Daniel Mills fc4377abaf Fix IO 2021-08-10 06:06:32 -04:00
Daniel Mills 97a62752c6 Compiles 2021-08-10 03:43:24 -04:00
Daniel Mills 87e1e55da7 Fix the board 2021-08-10 03:35:37 -04:00
Daniel Mills 5410cda182 World manager fixes 2021-08-10 03:35:31 -04:00
Daniel Mills ee240ca201 Fix post 2021-08-10 03:35:21 -04:00
Daniel Mills 98ffa0a256 Feature looping 2021-08-10 03:33:16 -04:00
Daniel Mills 2f450b7ea0 Iterate features 2021-08-10 03:29:27 -04:00
Daniel Mills 099b50dbfa Drop fix command 2021-08-10 03:23:28 -04:00
CocoTheOwner ece7a443fe another doc 2021-08-10 00:11:58 +02:00
CocoTheOwner 984f2669b8 Docs 2021-08-09 23:54:05 +02:00
CocoTheOwner 9b94b9e3fe Merge branch 'master' into gradle 2021-08-09 23:52:44 +02:00
CocoTheOwner a781bcb7f4 Simplify conversion & less error-prone
settings.gradle name is now used over user-definable one
2021-08-09 23:52:20 +02:00
CocoTheOwner b596f38ab5 Forgot one 2021-08-09 23:26:01 +02:00
CocoTheOwner f2b4d6f829 ok intellij if you so demand (still works) 2021-08-09 23:06:35 +02:00
CocoTheOwner d2d2dc64db Easier to yoink the build.gradle 2021-08-09 23:01:57 +02:00
Daniel Mills 5b8171a719 Fix structure imports 2021-08-09 16:28:10 -04:00
CocoTheOwner 838ad5ec1a More patches 2021-08-09 18:04:15 +02:00
CocoTheOwner 3d16323e33 Patch execute command 2021-08-09 17:53:36 +02:00
CocoTheOwner ba25eaa788 Patch for compat with new engine 2021-08-09 16:58:55 +02:00
CocoTheOwner 747412e522 Merge remote-tracking branch 'upstream/master' into irisCreateRework 2021-08-09 16:56:37 +02:00
Daniel Mills 059ff25d8e Fix dust 2021-08-09 09:59:12 -04:00
Daniel Mills 831143427d Strip engine components of parallax 2021-08-09 09:45:04 -04:00
Daniel Mills de51d4860e Drop parallax core 2021-08-09 09:43:30 -04:00
Daniel Mills 6569038777 Drop par manager 2021-08-09 09:43:15 -04:00
Daniel Mills 075aec3ac0 Mantle optimized iteration 2021-08-09 09:39:01 -04:00
Daniel Mills 734847db68 Object tweaks & mantle fixes 2021-08-09 09:38:52 -04:00
Daniel Mills a99f2b78a1 Drop gen access & merge with engine 2021-08-09 09:38:43 -04:00
Daniel Mills 037473f2e1 Cleanup components 2021-08-09 09:26:22 -04:00
Daniel Mills 378f735e9f Drop support for mutations 2021-08-09 09:11:09 -04:00
Daniel Mills 4cdac76ca5 Fixes 2021-08-09 09:09:45 -04:00
Daniel Mills 9ed3c8a6bb Mantle Feature Components 2021-08-09 09:07:42 -04:00
Daniel Mills 252d289a8d Framework remove parallax 2021-08-09 09:07:31 -04:00
Daniel Mills 832bad1fee Planned Structure support mantle 2021-08-09 09:07:22 -04:00
Daniel Mills d5b99316e5 Mantle & Engine Mantle & Engine changes 2021-08-09 09:07:13 -04:00
Daniel Mills 3723c5dae5 Replace placeObjects with useMantle 2021-08-09 09:06:57 -04:00
Daniel Mills 7c3fa7aef5 Player Matter 2021-08-09 09:06:49 -04:00
Daniel Mills 4ee31db7a0 Mantle flagging 2021-08-09 09:06:45 -04:00
Daniel Mills 0d8cb7393c Jigsaw Mantle Components 2021-08-09 09:06:31 -04:00
Daniel Mills 0d3d0a8da1 Mantle components 2021-08-09 09:06:24 -04:00
Daniel Mills e45eb7f2be Fixed dimension loaders 2021-08-09 07:14:36 -04:00
Daniel Mills 128e5c414c Fix parser issues 2021-08-09 07:14:27 -04:00
Daniel Mills 5a70d98c71 V* 2021-08-09 04:46:38 -04:00
Daniel Mills 69d4215397 V+ 2021-08-09 04:43:49 -04:00
Dan 722190305b Merge pull request #515 from VolmitSoftware/decomposed
The Decomposition of Iris
2021-08-09 04:40:21 -04:00
Daniel Mills 428d4859da Fix world data location 2021-08-09 04:38:24 -04:00
Daniel Mills 9455860554 Fixes 2021-08-09 04:27:43 -04:00
Daniel Mills eb4b42c089 Engine provider 2021-08-09 02:12:36 -04:00
Daniel Mills 79b3d7359a Fix closing issues 2021-08-09 02:12:30 -04:00
Daniel Mills c95ea19da1 Fix typing for goto 2021-08-09 01:15:37 -04:00
Dan a8b3f2af14 Merge pull request #516 from TOWUK/patch-1
api spigot 1.17.1
2021-08-09 01:10:50 -04:00
Daniel Mills 2b8ccc38bb Biome searching done 2021-08-09 01:10:12 -04:00
CocoTheOwner baebadb7a6 Patch command usage 2021-08-08 23:01:58 +02:00
CocoTheOwner f73db056f2 TabCompletions for create 2021-08-08 23:01:48 +02:00
CocoTheOwner 429ac1e71c Rework command 2021-08-08 21:54:19 +02:00
TOWUK 07cd91dda4 api spigot 1.17.1 2021-08-08 19:13:08 +03:00
Daniel Mills 956f61e775 "Working" 2021-08-08 08:14:59 -04:00
Daniel Mills 14fc2d2c70 Studio provider for engine bukkit 2021-08-08 08:01:34 -04:00
Daniel Mills 9c07240948 Fix world manager 2021-08-08 08:01:09 -04:00
Daniel Mills 7e2260578e Fix mantle df 2021-08-08 08:00:49 -04:00
Daniel Mills 7f2d781241 Fix effect manager 2021-08-08 08:00:35 -04:00
Daniel Mills df05ade4a7 Engine std check fixes 2021-08-08 08:00:09 -04:00
Daniel Mills e4b45e512f Studio check for headless 2021-08-08 07:58:09 -04:00
Daniel Mills 623bb52fd8 Studio check for engines 2021-08-08 07:58:02 -04:00
Daniel Mills ab809dabf8 Fix villager manager 2021-08-08 07:57:45 -04:00
Daniel Mills 2ebf26c156 Debug spawner boost fix 2021-08-08 07:57:40 -04:00
Daniel Mills 1ffc598fd4 Biome command fix 2021-08-08 07:57:34 -04:00
Daniel Mills 0495d954bc Studio method impl for bukkit 2021-08-08 07:52:45 -04:00
Daniel Mills cc6a7a6a72 Height getter 2021-08-08 07:52:35 -04:00
Daniel Mills b081bbb444 Engine fixes 2021-08-08 07:50:55 -04:00
Daniel Mills 69cbb262ba Patch core 2021-08-08 07:50:47 -04:00
Daniel Mills ba2a398da3 PAtch commands 2021-08-08 07:50:40 -04:00
Daniel Mills a8a87e7f79 Can you smell the rot? 2021-08-08 07:50:15 -04:00
Daniel Mills 2ffd1e6e47 Merge remote-tracking branch 'origin/master' 2021-08-08 06:26:02 -04:00
Dan efaebb43aa Merge pull request #510 from CocoTheOwner/compatbug
no async file write
2021-08-08 06:25:56 -04:00
CocoTheOwner d201350a08 Merge remote-tracking branch 'upstream/master' into compatbug 2021-08-08 12:01:14 +02:00
CocoTheOwner 81184d77fe This is better 2021-08-08 11:56:12 +02:00
Daniel Mills b9d60e0018 Fix build warnings 2021-08-08 04:54:39 -04:00
Daniel Mills f50ac49ede The engine is the framework 2021-08-08 04:53:14 -04:00
Dan 70d8b8a88a Merge pull request #506 from CocoTheOwner/UseToolbelt
Upgrade iris pregen & use toolbelt
2021-08-08 04:36:43 -04:00
Dan 535f1b5988 Merge pull request #512 from CocoTheOwner/whatArguse
Copy pasta eaten and processed :]
2021-08-08 04:34:21 -04:00
Daniel Mills 366c903f84 Mantle fixes 2021-08-08 03:44:25 -04:00
Daniel Mills 1781ff36c6 V+ 2021-08-08 01:57:46 -04:00
Daniel Mills 4e131d9a54 Fix async spawn issue on spigot 2021-08-08 01:13:53 -04:00
Daniel Mills 7f6fec5530 Fixes 2021-08-08 01:12:16 -04:00
Daniel Mills 2d43014029 Scripted spawners 2021-08-08 00:22:31 -04:00
Daniel Mills 33ea66ea88 Infinite energy option 2021-08-08 00:22:25 -04:00
Daniel Mills b74e449efd Object preprocessors 2021-08-07 23:41:26 -04:00
Daniel Mills afd4820713 Fixes for scripts 2021-08-07 23:41:21 -04:00
Daniel Mills 8e032fa26a Scripting engine 2021-08-07 23:11:09 -04:00
Daniel Mills 897669a069 Fix parallax closer 2021-08-07 22:42:17 -04:00
Daniel Mills 03ea78b40b Merge remote-tracking branch 'origin/master' 2021-08-07 21:18:45 -04:00
Daniel Mills 18465cf6cb Add BSF 2021-08-07 21:18:39 -04:00
CocoTheOwner ac6ccd2dc7 Another one 2021-08-07 21:07:18 +02:00
CocoTheOwner fb32d11d64 Copy pasta eaten and processed :] 2021-08-07 20:38:05 +02:00
CocoTheOwner ec75e911bd no async file write 2021-08-07 20:10:30 +02:00
CocoTheOwner 603f032e28 Merge remote-tracking branch 'upstream/master' into UseToolbelt 2021-08-07 20:04:48 +02:00
CocoTheOwner cf76e43898 Forgot to remove width&height from autcomplete 2021-08-07 19:53:32 +02:00
CocoTheOwner a0df548dd3 IrisPregenStart repl. IrisPregenCreate + fixes 2021-08-07 19:50:02 +02:00
CocoTheOwner dff6048a07 Rename class, update nodes, update messages 2021-08-07 19:25:59 +02:00
Dan 478d465cb6 Update README.md 2021-08-07 08:51:39 -04:00
Daniel Mills 271f308c36 V+ 2021-08-07 08:35:59 -04:00
Daniel Mills 956958fa0e Mantle Impl 2021-08-07 08:34:45 -04:00
Daniel Mills 6a68da0559 Cleanup 2021-08-07 08:16:24 -04:00
Daniel Mills 6683eee49a The Mantle API 2021-08-07 08:15:28 -04:00
Daniel Mills f258d5f932 Locking & Thread fixes 2021-08-07 08:15:09 -04:00
Daniel Mills 600995a220 Matter utilities 2021-08-07 08:15:03 -04:00
Daniel Mills 803dfed9f7 Mantle & Debugging 2021-08-07 02:48:36 -04:00
Daniel Mills 771cb45b49 V+ 2021-08-06 23:12:27 -04:00
Daniel Mills a2a7689881 Beautify updates keys 2021-08-06 23:08:48 -04:00
Daniel Mills e9f4c3d0c7 Cleanup 2021-08-06 22:15:11 -04:00
Daniel Mills a5ef5769b9 Tab complete for summons 2021-08-06 22:14:16 -04:00
Daniel Mills 9d251f94e5 Per chunk spawner cooldowns 2021-08-06 22:11:17 -04:00
Daniel Mills d1e8d52f5c Tweak particles 2021-08-06 21:01:11 -04:00
CocoTheOwner 14d2ae0c0e Use toolbelt instead of Irisworlds 2021-08-06 23:15:22 +02:00
CocoTheOwner dafa3bde8b Newline & append 2021-08-06 23:13:54 +02:00
CocoTheOwner cb923cef47 Details string klist to strinbuilder 2021-08-06 23:10:44 +02:00
CocoTheOwner 93888aeed3 nicer to work with 2021-08-06 22:58:09 +02:00
CocoTheOwner c41e34e9d8 Patches autocomplete with no worlds & getString cast error 2021-08-06 22:53:34 +02:00
CocoTheOwner 154aea7a61 Better autocompletions 2021-08-06 22:51:02 +02:00
CocoTheOwner bc53cd1aa4 Remove empty command descriptions 2021-08-06 22:50:52 +02:00
CocoTheOwner ef51aa7e98 oops 2021-08-06 22:22:56 +02:00
CocoTheOwner 705c5ce790 Fix console sender, add descriptions, add -here 2021-08-06 22:20:18 +02:00
CocoTheOwner 26dee50e68 Wikilink 2021-08-06 16:54:56 +02:00
CocoTheOwner be3d7ef11d Upgrade pregen command. PregenTask now takes width & height ins o radius 2021-08-06 16:52:15 +02:00
Daniel Mills ef5897969f Fixes 2021-08-06 10:22:28 -04:00
Daniel Mills d9cd0257ba Fix false desc warnings 2021-08-06 07:48:56 -04:00
Daniel Mills 95bb8cb1e5 Register managers 2021-08-06 07:42:08 -04:00
Daniel Mills e1aa2f651a Auto stash before merge of "master" and "origin/master" 2021-08-06 07:41:34 -04:00
Daniel Mills 03c535e80d Merge remote-tracking branch 'origin/master' 2021-08-06 07:39:39 -04:00
Daniel Mills 6f9c4f220e Oraxen Impl & Fixes 2021-08-06 07:39:28 -04:00
Daniel Mills 6f3813b685 Gradle 2021-08-06 07:38:47 -04:00
Dan 510dde6923 Merge pull request #500 from CocoTheOwner/noMaps
Remove dolphins so there's no crashes because of treasure chests.
2021-08-06 07:38:12 -04:00
CocoTheOwner a7761c1867 Villager trade warning system 2021-08-06 12:45:00 +02:00
CocoTheOwner a2af965bad This solves the warnings but it's probably better to <see desc>
Probably better to implement an annotation that cancels warnings for fields in classes that aren't Iris', such as bukkit packages. The implementation would be something like
@SuppressNoDesc
ItemStack items;
-> if !field.isAnnotationPresent(SND.class) { // error }
2021-08-06 12:38:50 +02:00
CocoTheOwner 28b6ed500b or not 2021-08-06 12:14:05 +02:00
CocoTheOwner 6991fa39cc more supression 2021-08-06 12:11:38 +02:00
CocoTheOwner 274469d299 This at least removes the warnings & replaces the description fields 2021-08-06 12:09:32 +02:00
CocoTheOwner a0b8581ec5 Nothin 2021-08-06 12:03:10 +02:00
CocoTheOwner b844a0e98d Simplify 2021-08-06 11:37:26 +02:00
CocoTheOwner e5f14ac176 Split into villager & dolphin manager 2021-08-06 11:36:12 +02:00
CocoTheOwner a1110a847d Patch dolphin, generify trades, move to dir, trades list repl. one 2021-08-06 11:33:12 +02:00
CocoTheOwner a3889fe369 Rename var in dimension 2021-08-06 11:31:25 +02:00
CocoTheOwner bd93918d9b Better 2021-08-06 11:00:09 +02:00
CocoTheOwner 421a6ba143 Merge remote-tracking branch 'upstream/master' into noMaps 2021-08-06 09:25:17 +02:00
Daniel Mills f091256edb Wow 2021-08-05 21:24:52 -04:00
CocoTheOwner c0e4733eb8 Auto stash before merge of "master" and "upstream/master" 2021-08-06 02:46:04 +02:00
Daniel Mills e636a6ad58 Docs 2021-08-05 20:06:22 -04:00
Daniel Mills 071617bfc5 Tile & Entity Matter 2021-08-05 20:03:50 -04:00
Daniel Mills 8a079c98cc Slices for entities & tiles 2021-08-05 19:27:30 -04:00
Daniel Mills fa95f96a6d Save / load tiles and normal entities 2021-08-05 19:27:16 -04:00
Daniel Mills c198abe5ac Merge remote-tracking branch 'origin/master' 2021-08-05 18:42:39 -04:00
Daniel Mills ef5ba0f1d1 Fixes 2021-08-05 18:42:34 -04:00
Daniel Mills fff7cd53cc INMS Drop 1.16 2021-08-05 18:42:17 -04:00
StrangeOne101 64ce5138e1 Re-added MythicMob support
- Re-added Mythic Mob support via everyone's favourite friend: Reflection
- Added room for Citizens to be supported using the same system
- MythicMobs can be spawned by adding `"specialType": "MythicMobs:<mobName>"` to your entity file. In future, Citizens could also be used here
2021-08-06 02:52:54 +12:00
Daniel Mills 3c07e7a0d8 More matter 2021-08-05 08:11:28 -04:00
Daniel Mills a1510ffcb5 Strange solutions to rotating matter objects 2021-08-05 07:41:20 -04:00
Daniel Mills 1b96bce44f Hunk rotation 2021-08-05 07:22:56 -04:00
Daniel Mills 294a5e39a6 Data formats Matter 2021-08-05 01:11:48 -04:00
Daniel Mills 65fdd6e25f Register studio as a root (also) 2021-08-04 23:52:05 -04:00
Daniel Mills 6d2ef1f985 V+ 2021-08-04 22:05:12 -04:00
Daniel Mills 14af19873c Fix nbt 2021-08-04 22:00:51 -04:00
Daniel Mills 2f5dd470d2 Fix typo closes #496 2021-08-04 21:55:00 -04:00
Daniel Mills dc4592ae4d Entity errors 2021-08-04 21:50:49 -04:00
Daniel Mills baa3cfa636 Fixes 2021-08-04 21:50:10 -04:00
Daniel Mills ad324df937 Matter apis 2021-08-04 20:19:22 -04:00
Daniel Mills f2b301cb17 Matter apis 2021-08-04 19:27:28 -04:00
Daniel Mills 6e369ea787 Its right 2021-08-04 18:32:02 -04:00
Daniel Mills f640abda91 Objectify objects 2021-08-04 18:30:58 -04:00
Daniel Mills 77f8f11d99 Move a lot of utilities in engine to util because they are utilities 2021-08-04 18:15:55 -04:00
StrangeOne101 12b4c27468 Object Command & Entity Spawn Changes (#492)
- Redid object paste command
    - Added rotate flag to paste with rotation
    - Added scale flag to paste with scale. Optional interpolation method can be used
    - Changes are now logged and can be reverted with the undo command
    - Fixed edit flag
    - Made the edit flag now update your wand even if it isn't in your hand
- Added object undo command
- Added object check command
- Added a surface target to spawn on for entities. This will fix mobs spawning in water
    - LAND for mobs that spawn on any land surface
    - ANIMAL for mobs that should only spawn on grass and dirt
    - WATER for mobs that should spawn in water
    - OVERWORLD for mobs that can spawn on both land and water (turtles, for example)
    - LAVA for mobs that can spawn on lava (striders, for example)
- Attempted to fix PAPI complaining about registering on the wrong thread
- Fixed console spam for entities (was due to the mount event being called async)
- Fixed grass paths and a few other update breaking blocks
    - Made it so if a block state changes on an update, it will now use as many as the other states as it can rather than not use anything
- Patch to stop people naming the world 'Iris'
2021-08-05 01:32:14 +12:00
Daniel Mills 75f3073cbf V+ 2021-08-04 02:24:11 -04:00
Daniel Mills 5eddfaed98 Stop logging jigsaw places 2021-08-04 02:20:52 -04:00
Daniel Mills 89b4fdef56 Custom sound for pearl so you know its working 2021-08-04 02:19:24 -04:00
Daniel Mills 6f08c1f758 Actually gen 2021-08-04 02:13:08 -04:00
Daniel Mills 1155789a75 Stronkholds 2021-08-04 02:07:55 -04:00
Daniel Mills 8c4a74179d Better Rates 2021-08-04 01:47:36 -04:00
Daniel Mills bdbc94e54a Energy tweaks 2021-08-04 01:18:45 -04:00
Daniel Mills 30f880361e Fix multipliers 2021-08-04 01:16:42 -04:00
Daniel Mills c5ac347538 Entity logic 2021-08-04 01:07:07 -04:00
Daniel Mills 008da9467f Feature hack 2021-08-03 23:03:40 -04:00
Daniel Mills ee78249c2f Fix feature placements 2021-08-03 23:02:46 -04:00
Daniel Mills 412c67b47a Objects "Matter" 2021-08-03 21:45:09 -04:00
Daniel Mills f4191ad984 test 2021-08-03 20:01:21 -04:00
Daniel Mills ea8d1100ac Fix feature searching 2021-08-03 18:11:57 -04:00
Daniel Mills 20e8c033cc Fixes 2021-08-03 18:09:32 -04:00
Daniel Mills ff2d10e0e1 Cooldown fixes 2021-08-03 18:03:31 -04:00
Daniel Mills 672c39e58d Faster Interpolatin 2021-08-03 18:03:27 -04:00
Dan 36d3849118 Merge pull request #491 from CocoTheOwner/updateBuild
moved my folder
2021-08-03 01:41:15 -04:00
Daniel Mills 43d7dfde4c Cleanup sources 2021-08-03 01:40:35 -04:00
Daniel Mills 51a056b3d7 Spawners in objects closes 2021-08-03 01:37:03 -04:00
Daniel Mills 6e22d6687c Update meta 2021-08-02 20:31:27 -04:00
Daniel Mills 369fdf07e2 Fixes 2021-08-02 20:25:00 -04:00
Daniel Mills 8c6c1b3ccc Proguard (optimizatin & shrinking only) no obfuscation :D 2021-08-02 19:50:56 -04:00
Daniel Mills 160bdf5472 Cleanup sources 2021-08-02 19:50:41 -04:00
Daniel Mills 18c70002cb Cleanup 2021-08-02 18:36:56 -04:00
Daniel Mills 4b8cfd9fdd Aquire engine data in expressions 2021-08-02 17:04:47 -04:00
Daniel Mills ffa5fd5b2a Insane Loader Propagation 2021-08-02 16:32:57 -04:00
Daniel Mills 9fb4798c77 Streamed expressions 2021-08-02 15:59:27 -04:00
Daniel Mills a6039278c3 Fix data meta 2021-08-02 15:57:18 -04:00
Daniel Mills c50702c83b Fix atomic cache not transient 2021-08-02 15:54:45 -04:00
Daniel Mills d4c1e62c72 Fix object registry 2021-08-02 15:54:38 -04:00
Daniel Mills 488c609109 Registry list abstraction 2021-08-02 15:45:22 -04:00
Daniel Mills 0a6e24104e Support new loaders 2021-08-02 15:27:05 -04:00
Daniel Mills d6acac0208 Better schema building 2021-08-02 15:26:54 -04:00
Daniel Mills b02f61ef21 Data loader abstraction 2021-08-02 15:26:46 -04:00
Daniel Mills eb82b44726 Cleanup 2021-08-02 01:14:52 -04:00
Daniel Mills ca035f9447 IRis Expressions 2021-08-02 01:13:55 -04:00
Daniel Mills 19aa3c0854 Tristarcast & Interpolation improvements 2021-08-02 01:13:37 -04:00
Daniel Mills 93675d90bb 3D Caching for procedural streams 2021-08-02 01:13:07 -04:00
Daniel Mills 0d90c1d960 Hunk interp 2021-08-02 01:12:51 -04:00
Daniel Mills d826f968b5 Cavern json objects 2021-08-02 01:12:40 -04:00
Daniel Mills f682d7489f Cavern tests 2021-08-02 01:12:25 -04:00
Daniel Mills 649b1adfdd Move project related code in their own package 2021-08-02 01:12:13 -04:00
Daniel Mills 3d67ea736f Add dep Paralithic for expressions 2021-08-02 01:11:32 -04:00
Daniel Mills ba88f837fe Cave zones & force main world 2021-08-01 16:20:46 -04:00
Daniel Mills 6728703473 Island mode improvements 2021-08-01 15:01:58 -04:00
Daniel Mills 55521afe35 Fixes 2021-08-01 13:45:01 -04:00
Daniel Mills 8820b02406 Engine data saving 2021-08-01 13:36:28 -04:00
Daniel Mills e79ba6117b V+ 2021-07-31 22:50:36 -04:00
Daniel Mills 668d29a9d7 Hotloader improvements 2021-07-31 22:44:46 -04:00
Daniel Mills 6b97acdb50 Fixes 2021-07-31 22:32:14 -04:00
Daniel Mills 9a11021560 Fixes 2021-07-31 20:44:52 -04:00
Daniel Mills ee18217520 Entities 2021-07-31 19:41:12 -04:00
Daniel Mills 16de60f82d Spawners 2021-07-31 12:23:35 -04:00
Daniel Mills 8cd94f976c Identity for regions & biomes 2021-07-31 11:27:11 -04:00
Daniel Mills 9ab0662cbf V+ 2021-07-31 09:31:19 -04:00
Daniel Mills 5cceed1fd7 Fix plax cmods 2021-07-31 09:30:47 -04:00
Daniel Mills 03a1b51bd7 Fix parallax noise feature artifact holes / spikes
It was a really weird caching issue
2021-07-31 08:58:54 -04:00
Daniel Mills 1edf567ea7 Allow disabling some locks in settings for speedups 2021-07-31 08:55:53 -04:00
CocoTheOwner fa73eddf50 moved my folder 2021-07-31 14:37:54 +02:00
Daniel Mills 1923ae6f4b Revert "Lock the piss out of the parallax layer "
This reverts commit 229f3dc061.
2021-07-31 08:20:57 -04:00
Daniel Mills 229f3dc061 Lock the piss out of the parallax layer 2021-07-31 08:18:21 -04:00
Daniel Mills cc95042a94 Use a synced list 2021-07-31 08:18:12 -04:00
Daniel Mills 302f54971d Tweak feature iteration 2021-07-31 08:18:00 -04:00
Daniel Mills dff88403b5 I'm not proud of this 2021-07-31 08:10:45 -04:00
Daniel Mills cd3f9af232 Fix tree type size matcher 2021-07-30 10:22:50 -04:00
Daniel Mills b217162cad Get the real engine 2021-07-30 10:12:33 -04:00
Daniel Mills 7d24a61ae8 Fixes 2021-07-30 10:04:58 -04:00
Daniel Mills ca1b37dbcf Block data support native oraxen ids 2021-07-30 10:04:50 -04:00
Daniel Mills 0e887fb2ea Register Oraxen link 2021-07-30 10:04:41 -04:00
Daniel Mills a122adf05d Oraxen 2021-07-30 09:58:54 -04:00
Daniel Mills 04fc1e25e4 Coord annotations 2021-07-30 09:37:21 -04:00
Daniel Mills cff402affb Attempt block drops 2021-07-30 09:33:50 -04:00
Daniel Mills e1e0368f8c Papi support 2021-07-30 09:19:28 -04:00
Daniel Mills 8ee39d3822 Drop BKCommonLib (unused) 2021-07-30 08:52:06 -04:00
Daniel Mills e2b533db64 Fix 2021-07-30 08:50:18 -04:00
Daniel Mills fc5db74f16 Fix incorrect sapling placements 2021-07-30 08:49:46 -04:00
Daniel Mills 42e62a3698 Real class height placer 2021-07-30 08:48:29 -04:00
Daniel Mills 62f03356ca V+ 2021-07-30 07:07:06 -04:00
Daniel Mills 25bb0912fb Fix villages re-placing on top of each other 2021-07-30 06:17:56 -04:00
Dan beff1d8d42 Merge pull request #485 from CocoTheOwner/fixTreeNPE
Fix tree NPE
2021-07-30 04:49:10 -04:00
Daniel Mills 7dc56cc075 Cleanup 2021-07-30 04:40:26 -04:00
Daniel Mills 394d653978 Hunk grow interp 2021-07-30 04:40:07 -04:00
Daniel Mills 742fa27029 Fix engine hotloading 2021-07-30 04:39:56 -04:00
Daniel Mills 18b962dc57 Speed up profiling 2021-07-30 04:39:45 -04:00
Daniel Mills 233a179dad Pregen progress 2021-07-30 04:39:33 -04:00
Daniel Mills 9cc76c5ee6 Better warning 2021-07-30 01:52:05 -04:00
Daniel Mills 401ba40373 Shutdown render threads 2021-07-29 14:58:02 -04:00
Daniel Mills 6dad50aab1 Fix thread switch 2021-07-29 14:48:32 -04:00
Daniel Mills 9a43f868aa Hotload Iris Vision 2021-07-29 14:32:17 -04:00
Daniel Mills 294e9bb590 Fix out of date plugin ymls 2021-07-29 14:16:15 -04:00
Daniel Mills d101311109 V+ 2021-07-29 14:05:19 -04:00
CocoTheOwner e0e920bfe0 return, dumb 2021-07-29 11:28:25 +02:00
CocoTheOwner 3a090d7c1d Fix tree NPE 2021-07-29 11:07:27 +02:00
Daniel Mills dc851d856d Performance Improvements 2021-07-29 04:24:28 -04:00
Daniel Mills 2f8fe43ea3 Clean 2021-07-28 23:33:21 -04:00
Daniel Mills 959f96dd74 Jigsaw is faster 2021-07-28 23:32:14 -04:00
Daniel Mills 6620d2d023 Transient fix 2021-07-28 23:20:18 -04:00
Daniel Mills 408742494a Fixes 2021-07-28 21:35:22 -04:00
Daniel Mills f7ae9c06d8 Fix post mod issues 2021-07-28 21:34:02 -04:00
Daniel Mills b4c0d8e829 Fix build env 2021-07-28 20:41:25 -04:00
Daniel Mills b2a410d6cf Proper fracturing for max fracture accuracy 2021-07-28 20:18:10 -04:00
Daniel Mills 16fa453163 Fixes 2021-07-27 18:53:50 -04:00
Daniel Mills fc678684f5 Cleanup 2021-07-27 17:34:51 -04:00
Dan 8f7db7e52f Merge pull request #482 from VolmitSoftware/performance
Iris MPC
2021-07-27 17:33:09 -04:00
Daniel Mills e9d6cf278a Fix cave mod MPC 2021-07-27 17:32:03 -04:00
Daniel Mills d1c6f5f98e MPC Modifier support 2021-07-27 17:24:24 -04:00
Daniel Mills 48f581525e MPC Actuator support 2021-07-27 17:24:17 -04:00
Daniel Mills 8d967856b5 Support switching on and off MPC 2021-07-27 17:24:06 -04:00
Daniel Mills ee83cf2989 Sync LTC for CraftBukkit 2021-07-27 17:04:14 -04:00
Daniel Mills cb304d35e2 Desync terrain actuator 2021-07-27 17:04:05 -04:00
Daniel Mills 65185c5854 Multicore caves 2021-07-27 17:01:22 -04:00
Daniel Mills 91526674be Multicore terrain 2021-07-27 17:00:27 -04:00
Daniel Mills 3e11ff65db Multicore post 2021-07-27 17:00:19 -04:00
Daniel Mills 4a3ac17a2f Precache height 2021-07-27 17:00:12 -04:00
Daniel Mills 2283f1ddc9 Update metrics 2021-07-27 17:00:06 -04:00
Daniel Mills ae78887746 V+ 2021-07-27 15:42:07 -04:00
Daniel Mills 9de4bf839f Improve feature performance 2021-07-27 15:38:37 -04:00
Daniel Mills 295e51181b Fix tree placement 2021-07-27 15:27:21 -04:00
Daniel Mills 287e3a0090 Fixes 2021-07-27 15:21:00 -04:00
Daniel Mills 4d150e71a3 Don't iterate nothing 2021-07-27 15:20:33 -04:00
Daniel Mills 0debbc263e Fix false error detection 2021-07-27 15:20:17 -04:00
Daniel Mills a9e5e40a54 Fix add issue with blockdata 2021-07-27 15:04:28 -04:00
Daniel Mills cf61080980 Valid predicates for saplings 2021-07-27 15:04:20 -04:00
Daniel Mills 3e33731cf0 Update 2021-07-27 14:45:30 -04:00
Daniel Mills d7cda90acd Cleanup 2021-07-27 14:45:19 -04:00
Daniel Mills 9a7dcc8c1a Finalize 2021-07-27 14:44:07 -04:00
Dan d477121790 Merge pull request #481 from StrangeOne101/master
Decorator Updates
2021-07-27 14:43:05 -04:00
Daniel Mills 60e7a21e39 Cleanup 2021-07-27 14:42:43 -04:00
Daniel Mills b915ca2add Strip some comments 2021-07-27 14:41:31 -04:00
Daniel Mills e90ef7aaee Prevent self-chain firing events 2021-07-27 14:40:36 -04:00
Daniel Mills e918daaa9b This is not part of the tree. 2021-07-27 14:38:21 -04:00
Dan 83b4f645db Merge pull request #434 from CocoTheOwner/saplingOverrides
Add sapling / tree overrides WIP
2021-07-27 14:37:17 -04:00
StrangeOne101 1797df7017 Reblackhole 2021-07-27 14:40:14 +12:00
StrangeOne101 3beda9c201 Black Hole 2021-07-27 14:33:46 +12:00
CocoTheOwner 68e6bd668c Sync grow event, patch set fnction, path blockstateslist 2021-07-26 20:20:49 +02:00
CocoTheOwner 696cf61fe9 Remove log on creation 2021-07-26 20:02:59 +02:00
Daniel Mills 7fe56766fc V+ 2021-07-26 13:48:26 -04:00
Daniel Mills 1e62f8c951 Stop duplicate features from hitting each other 2021-07-26 13:21:01 -04:00
Daniel Mills 82b995f96f Parallax customize vacuum interp 2021-07-26 13:20:50 -04:00
Daniel Mills 05aa0c2482 Safe plax chunk meta 2021-07-26 13:13:53 -04:00
Daniel Mills bca5f4bec4 Fix NPE for now 2021-07-26 13:06:16 -04:00
Daniel Mills 8b599f78c0 Error detection when opening a studio world closes #466 2021-07-26 13:04:27 -04:00
StrangeOne101 38776e74bc Decorator Changes & Jigsaw Fix
- Fixed Jigsaw pieces with rotation disabled rotating randomly. They will now inherit the parent's rotation
- Fixed pallete of blocks in stacked blocks not being chosen randomly on the Y axis

Fixes for issues in the last commit:
- Fixed CEILING decorators not working with scaleStack
2021-07-27 04:12:40 +12:00
StrangeOne101 21ce09a5b5 Added scaleStack property to decorators
- Added scaleStack to decorators. It allows the size of the stack to scale to the size of the cave, using stackMin and stackMax as a min/max percentage
- Fixed stacks missing a block in size when the top block isn't defined
- Changed decorator stacks of a single block to place the top block instead of the body
- Fixed tab sound playing to other players
2021-07-27 04:12:40 +12:00
StrangeOne101 5fabec6a42 Minor patches to decorators 2021-07-27 04:12:40 +12:00
StrangeOne101 64f361d4b1 Cave Decorator Changes
- Surface decorators are no longer placed below liquids in caves
- SEA_FLOOR and SEA_SURFACE decorators now function within caves relative to the cave liquid
- Fixed stacked decorators going above the height of caves and the map
2021-07-27 04:12:40 +12:00
Daniel Mills f8ae842971 Revert "You cant call gson, the exception is that the string is too long,"
This reverts commit ca01d6efb8.
2021-07-26 12:05:09 -04:00
Daniel Mills ca01d6efb8 You cant call gson, the exception is that the string is too long, 2021-07-26 12:04:37 -04:00
Dan 498ab6d165 Merge pull request #472 from CocoTheOwner/betterObjectPaste
Reduce message spam, reduce code complexity, stop opening obj twice
2021-07-26 11:59:27 -04:00
CocoTheOwnerandStrangeOne101 8e8524ccc8 Patch pregen command descriptions (#477)
* Patch pregen command descriptions

* Update CommandIrisPregen.java

Co-authored-by: StrangeOne101 <strange.toby@gmail.com>
2021-07-26 23:01:38 +12:00
CocoTheOwner 714936f710 WTF to JSON 2021-07-26 22:58:56 +12:00
CocoTheOwner 11c0477a34 Patch entity spawns 2021-07-26 19:06:16 +12:00
CocoTheOwner 4f65c6bbcc improve villager placements 2021-07-26 17:16:57 +12:00
CocoTheOwner c7ee183fe4 Reduce complexity, similarly done to other cmds.
Removed "Hotloaded!" As that is also printed by the studio world's hotload function, now players are getting it twice.
2021-07-26 17:16:37 +12:00
CocoTheOwner 80545ce1f2 1 more todo 2021-07-25 23:40:38 +02:00
CocoTheOwner c80edcfe9f Patch set command (I get what you mean now) 2021-07-25 23:37:29 +02:00
CocoTheOwner 74318fb3c0 Call new bukkit event with the grow event data from here 2021-07-25 23:36:12 +02:00
CocoTheOwner 83a8942724 ok 2021-07-25 23:21:43 +02:00
CocoTheOwner 48f57068d8 width & depth must be equal, not smaller 2021-07-25 23:17:19 +02:00
CocoTheOwner 3f85916764 Reduce message spam, reduce code complexity, stop opening obj twice 2021-07-25 22:41:17 +02:00
Daniel Mills ab0f544267 V+ 2021-07-25 12:56:48 -04:00
Daniel Mills 14e16c874e More noise generators 2021-07-25 11:31:39 -04:00
CocoTheOwner 7fb87ad67f Merge branch 'master' into saplingOverrides 2021-07-25 15:28:54 +02:00
Dan 1b85417ae6 Merge pull request #467 from CocoTheOwner/removeregen
Remove regen command from registry
2021-07-25 09:17:38 -04:00
CocoTheOwner 8d41e6fece Remove regen command from registry 2021-07-25 13:28:54 +02:00
Daniel Mills ab2906991d Cleanup 2021-07-24 16:40:58 -04:00
Daniel Mills ac85c75cc4 Load all biomes for data packs, not just thought used ones 2021-07-24 11:49:47 -04:00
Daniel Mills 7f1fae307c Stop null biomes from ever being set into chunks 2021-07-24 11:49:17 -04:00
Daniel Mills cce41082a1 Spectator mode 2021-07-24 11:49:03 -04:00
Daniel Mills 902a489b04 Actually use priority mburst 2021-07-24 11:19:05 -04:00
Daniel Mills 30006fe520 Project creation fixes 2021-07-24 11:18:58 -04:00
Daniel Mills 2ce1bb9c3f Parallax features working with biomes! 2021-07-24 11:18:45 -04:00
Daniel Mills c468fa2a95 Fix multipliers 2021-07-24 09:50:57 -04:00
Daniel Mills 06ef342953 Fix NPE 2021-07-24 09:49:03 -04:00
Daniel Mills 02121105de Merge remote-tracking branch 'origin/master' 2021-07-24 09:46:42 -04:00
Daniel Mills e24788157a Plax typo 2021-07-24 09:46:31 -04:00
Daniel Mills 63905c608e Remove unused addNoise field on pos 2021-07-24 09:46:04 -04:00
Daniel Mills 69127e6952 Fix progress issues with pregen headless world inits 2021-07-24 09:45:52 -04:00
Daniel Mills 87a84f02a3 Board tweaks 2021-07-24 09:42:39 -04:00
Daniel Mills 0b5bea9965 Support features with fractured radii 2021-07-24 09:42:32 -04:00
Daniel Mills 0d5e3a080c Parallax fixes 2021-07-24 09:42:19 -04:00
Daniel Mills a4ba07de99 Fix world events 2021-07-24 09:42:06 -04:00
Daniel Mills 970a5ec763 Support creation of studio worlds through tk 2021-07-24 09:41:39 -04:00
StrangeOne101 6f338c44c4 Everyone and their neighbor loves typos 2021-07-25 01:34:28 +12:00
Daniel Mills f360fadd3f Better iris noise rendering 2021-07-23 18:50:25 -04:00
Daniel Mills 846fa4722b Parallax fiaster 2021-07-23 18:25:29 -04:00
Daniel Mills 9752f1d492 Fix iris loot & update blocks 2021-07-23 17:44:13 -04:00
Daniel Mills ce2d386b7e Iris debug toggle 2021-07-23 17:44:03 -04:00
Daniel Mills 993d433d03 Fix gradle 2021-07-23 17:22:24 -04:00
Daniel Mills ede344db3a V+ 2021-07-23 16:15:31 -04:00
Daniel Mills 6c397da1f8 INMS Additions 2021-07-23 16:13:17 -04:00
Daniel Mills 1d9862fe11 1.16 Support regress 2021-07-23 16:06:10 -04:00
Daniel Mills 2d3db00639 Fixes 2021-07-23 09:40:06 -04:00
Daniel Mills 7269233134 Fix goto issues 2021-07-23 09:34:20 -04:00
Daniel Mills 27be0ad808 Object tab complete 2021-07-23 09:14:52 -04:00
Daniel Mills 51e875694c Jigsaw tab complete place 2021-07-23 09:14:45 -04:00
Daniel Mills 92d64d77c5 Object paste tab complete 2021-07-23 09:14:34 -04:00
Daniel Mills 579e9e0b8c Possible keys search 2021-07-23 09:14:22 -04:00
Daniel Mills 25b9c491c3 Fix bitwise calculator 2021-07-23 08:55:53 -04:00
Daniel Mills 8f1b48b0ab Fix paper async chunk errors 2021-07-23 08:49:03 -04:00
Daniel Mills 611d3c3b63 Retry on paper load failures 2021-07-23 08:38:32 -04:00
Daniel Mills 3a658e10f1 Fix possible load keys loading all data folders 2021-07-23 08:36:42 -04:00
Daniel Mills 92a182e5a8 Fix rotations? 2021-07-22 21:27:14 -04:00
Daniel Mills 954f0cc26f Settings changes and fix npes 2021-07-22 21:26:01 -04:00
Daniel Mills 5e0b4e1be6 Try get chunk 2021-07-22 21:01:10 -04:00
Daniel Mills c72511da6a Tests 2021-07-22 20:57:10 -04:00
Daniel Mills 15c65ad4e1 Synchronize sections test? 2021-07-22 20:50:43 -04:00
Daniel Mills d06657e2ff Zero write detection? 2021-07-22 20:49:00 -04:00
Daniel Mills 5dae1ee34f ffs 2021-07-22 20:37:02 -04:00
Daniel Mills 88a5d33bad f 2021-07-22 20:31:49 -04:00
Daniel Mills 42e02a6129 Atomic MCA Data 2021-07-22 20:27:15 -04:00
Daniel Mills 7d56164882 Attempt to fix concurrency issues 2021-07-22 20:17:11 -04:00
Daniel Mills 3e60ad21bf Fix npe 2021-07-22 20:07:38 -04:00
Daniel Mills 55c4e9635a Commands 2021-07-22 20:07:18 -04:00
Daniel Mills 101d0212a7 Pullbuild 2021-07-22 19:43:30 -04:00
Daniel Mills f0338b5247 More gen errors 2021-07-22 19:38:49 -04:00
Daniel Mills 80136b4aff Merge remote-tracking branch 'origin/master' 2021-07-22 19:23:35 -04:00
Daniel Mills f0a89e99f5 Error Test 2021-07-22 19:23:31 -04:00
CocoTheOwner 09f830c315 patch faulty null exit 2021-07-22 23:40:25 +02:00
StrangeOne101 ace92f8be0 Fixes grow on trees (#458)
- Made the download command easier to use with less errors than before
- Usage is now `/iris download <repo> [branch=master]`
- Fixed multiplicitive being useless in noise generators
2021-07-23 03:34:31 +12:00
Daniel Mills 7e08f495a7 Schemas for mods 2021-07-22 11:13:26 -04:00
Daniel Mills a8299b4f54 Iris Mods 2021-07-22 11:12:17 -04:00
Daniel Mills bdb00b8d58 Formatting for objects 2021-07-22 10:56:01 -04:00
Daniel Mills 4e18127371 Clamp heights to prevent issues with negative heights / overflowing 2021-07-22 10:54:51 -04:00
CocoTheOwner e5165e6fba add some todos 2021-07-22 16:25:24 +02:00
Daniel Mills 77dc237549 Add a description for terrain modes 2021-07-22 10:18:11 -04:00
Daniel Mills e3cc04f538 Fix focus mode causing failing chunks 2021-07-22 09:21:38 -04:00
Dan 322d1ad75c Merge pull request #454 from CocoTheOwner/bugreportfix
Allow people to paste screenshots in report
2021-07-22 08:34:12 -04:00
Daniel Mills 4767903a07 V+ 2021-07-22 08:31:07 -04:00
CocoTheOwner 0064ce97f0 Allow people to paste screenshots in report 2021-07-21 21:10:42 +02:00
CocoTheOwner 71f067f678 Next step to free sizes for saplongs 2021-07-21 21:10:16 +02:00
CocoTheOwner 8f37dc8570 Cleanup & fix 2021-07-21 18:24:09 +02:00
CocoTheOwner b15b640652 Probably wise to quicksave 2021-07-21 18:12:45 +02:00
CocoTheOwner 3e892f30ca Remove requireNonNull and do safer 2021-07-21 16:59:46 +02:00
CocoTheOwner 20f3efced9 Convert to toolbelt 2021-07-21 16:58:09 +02:00
CocoTheOwner 25bcd0f2a9 Merge remote-tracking branch 'upstream/master' into saplingOverrides 2021-07-21 16:48:22 +02:00
Daniel Mills d990bf794a Merge remote-tracking branch 'origin/master' 2021-07-21 10:39:44 -04:00
Daniel Mills aa25feb57e Fixes 2021-07-21 10:39:39 -04:00
Dan b203fc7c68 Merge pull request #443 from CocoTheOwner/fixCompoundNPE
Fix NPE by checking if null before calling on it.
2021-07-21 09:37:35 -04:00
Dan 9298e045d3 Merge pull request #442 from CocoTheOwner/fixVolmitSender
Fix sender.player() call without checking if player
2021-07-21 09:37:15 -04:00
Dan e3255588d3 Merge pull request #448 from CocoTheOwner/infoImprovement
Improve some info logs
2021-07-21 09:34:41 -04:00
Dan 514f23cf4d Merge pull request #436 from CocoTheOwner/better-bugreports
Update bug reports & feature requests
2021-07-21 09:34:16 -04:00
Daniel Mills ab206b2abf Stop threads on vision shutdown 2021-07-21 09:13:05 -04:00
Daniel Mills 2f8a65d364 Only install datapacks on init if it is studio 2021-07-21 08:39:09 -04:00
Daniel Mills a30e47d094 Shutdown threads on iris disable 2021-07-21 08:37:35 -04:00
Daniel Mills 0a78a87a17 Stop recreating threads without shutting down engine targets 2021-07-21 08:37:26 -04:00
Daniel Mills 886ec9c574 V+ 2021-07-21 08:12:01 -04:00
Daniel Mills 434fe894bc Precache Noise double chunk speed! 2021-07-21 08:11:47 -04:00
Daniel Mills cde9dbe406 Fix parallax issues 2021-07-21 08:10:26 -04:00
Daniel Mills c5262f188e Fix JVM Warnings 2021-07-21 08:10:11 -04:00
Daniel Mills 8f57364547 Verify MCA Chunks 2021-07-21 08:09:57 -04:00
CocoTheOwner eb193f230b Re-enable dirty placer. needs fixin' 2021-07-21 11:40:16 +02:00
CocoTheOwner 6a868f975e Rename treeoptions & improve debug 2021-07-21 11:39:27 +02:00
CocoTheOwner 66a8db598f Require toggle in treeSettings 2021-07-21 11:34:34 +02:00
CocoTheOwner bfa13583e3 Sapling now called tree 2021-07-21 11:33:42 +02:00
CocoTheOwner d865e646b9 Remove @Required from object place chance (default = 1 already) 2021-07-21 11:30:26 +02:00
CocoTheOwner f630537897 ANY treetype 2021-07-21 11:27:21 +02:00
CocoTheOwner aaf7ae8fc3 Remove unused, add & implement ANY treeSize 2021-07-21 11:25:22 +02:00
CocoTheOwner 52cba23190 Add object placement to dimension
It is not enabled to place objects, but will work with sapling overrides
2021-07-21 11:24:54 +02:00
CocoTheOwner c48c74a0cf Remove saplings, update debug, improve object placing functions 2021-07-21 11:10:43 +02:00
CocoTheOwner 9c6c27909d Improve some info logs 2021-07-20 23:29:10 +02:00
CocoTheOwner 3ba3e5cf76 Fix NPE by checking if null before calling on it. 2021-07-20 22:04:21 +02:00
CocoTheOwner df4040a96a Fix sender.player() call without checking if player 2021-07-20 21:50:33 +02:00
CocoTheOwner 6932e468cf comments, debug. 2021-07-20 21:46:56 +02:00
CocoTheOwner a82279b068 Refactor, cleanup, debug 2021-07-20 21:25:28 +02:00
Daniel Mills 0ae7b9c120 A Friendly reminder... 2021-07-20 14:28:06 -04:00
Daniel Mills 72f86069a2 Dont lock objects (for now) 2021-07-20 14:27:59 -04:00
Daniel Mills 509c32db70 Close engine targets on shutdown 2021-07-20 14:27:45 -04:00
Daniel Mills 7fa45c0658 Stop bounds enforcement 2021-07-20 14:27:36 -04:00
Daniel Mills 4e81a6af83 Don't interpolate slope 2021-07-20 13:48:47 -04:00
Daniel Mills 66d3eb3400 Shutdown engine threads properly 2021-07-20 13:48:34 -04:00
Daniel Mills 7e36cb76ba Lock accessors 2021-07-20 13:48:25 -04:00
Daniel Mills fc47651c33 Fix 2021-07-20 13:28:17 -04:00
Daniel Mills 6948d70d94 V+ 2021-07-20 12:01:10 -04:00
Daniel Mills eee2db8ad9 Fix Decays 2021-07-20 11:52:36 -04:00
Daniel Mills 318a36a831 Only use hybrid headless if mca is enabled 2021-07-20 11:40:01 -04:00
Daniel Mills 4d90bd9e31 Cleanup Code 2021-07-20 11:36:44 -04:00
Daniel Mills 4e81151e11 Config fixes for pregen 2021-07-20 11:35:40 -04:00
Daniel Mills 18195b778a Cleanup old compat 2021-07-20 11:33:02 -04:00
Daniel Mills 326bddad2a Thread counts synced 2021-07-20 11:19:47 -04:00
Daniel Mills f4b41a43cf Link pregen 2021-07-20 11:18:55 -04:00
Daniel Mills 38c762f775 Drop doaflip again 2021-07-20 11:18:44 -04:00
Daniel Mills 9833d466c4 Iris Syndicate 2021-07-20 11:18:35 -04:00
Daniel Mills 4d965333ef Revert "Drop do a flip "
This reverts commit 9ed246e482.
2021-07-20 08:56:17 -04:00
CocoTheOwner a6ec18690d Fix tree sapling detection math 2021-07-20 14:25:34 +02:00
Daniel Mills d3fe0b48d8 Pregenerator tracking nets 2021-07-20 04:56:54 -04:00
Daniel Mills 35220e1c4d Slave pregen method 2021-07-20 04:56:45 -04:00
Daniel Mills de7bc9870d Turbo connectivity 2021-07-20 04:56:37 -04:00
Daniel Mills 680a3a5d13 Turbo commands 2021-07-20 04:56:31 -04:00
Daniel Mills cf8612c99f Net listeners 2021-07-20 04:56:20 -04:00
Daniel Mills e817d3355c GRTF 2021-07-20 04:48:23 -04:00
Daniel Mills 53b7f33ec4 IO Write file from stream 2021-07-20 04:48:15 -04:00
Daniel Mills 5ed9a29f81 Color fixes 2021-07-20 03:02:18 -04:00
Daniel Mills 89406c4754 Docs 2021-07-20 02:59:49 -04:00
Daniel Mills b27b26837f Coord docs & fix serious parallax issue 2021-07-20 02:59:38 -04:00
Daniel Mills 0595008fce Stop serious memory leaks 2021-07-20 02:59:15 -04:00
Daniel Mills d6b2e019c7 Headless look for existing chunks 2021-07-20 02:58:57 -04:00
Daniel Mills 25cfb1f5c0 Plx fixes 2021-07-20 02:58:44 -04:00
Daniel Mills 10a159b315 Decorator doc 2021-07-20 02:58:17 -04:00
Daniel Mills 60c113b0e8 Allow reading region chunks without loading mca 2021-07-20 02:58:11 -04:00
Daniel Mills ec4330aea7 Actuator docs 2021-07-20 02:58:01 -04:00
Daniel Mills f263fe30a4 PRegen tweaks 2021-07-20 02:57:52 -04:00
Daniel Mills 0ec2f68a96 Proper plax coord checking 2021-07-20 00:14:39 -04:00
Daniel Mills 1156b909eb Save debugging for plax 2021-07-20 00:14:26 -04:00
Daniel Mills 9ed246e482 Drop do a flip 2021-07-19 23:55:28 -04:00
CocoTheOwner c342c5c4f6 fixes and the majority of treesize retrieval 2021-07-19 19:03:25 +02:00
Daniel Mills 3b7ef5b262 Coolstuff 2021-07-19 11:05:52 -04:00
Daniel Mills 79bf3d8d4f f 2021-07-19 10:47:20 -04:00
Daniel Mills cfe91a8722 Le Pregenerator with headless mode 2021-07-19 10:46:03 -04:00
Daniel Mills c3e2d34372 APIs & such 2021-07-19 10:45:52 -04:00
Daniel Mills 56b6ee5d2a Merge remote-tracking branch 'origin/master' 2021-07-19 10:45:21 -04:00
Dan 3f34ac1c75 Update README.md 2021-07-19 10:43:48 -04:00
Daniel Mills be5015d16b Patch grasspath 2021-07-19 09:27:43 -04:00
Dan 2c1d4cfc40 Merge pull request #437 from VolmitSoftware/new-pregenerator
...
2021-07-19 06:42:27 -04:00
Daniel Mills 5bf75e122c Tweaks 2021-07-19 06:40:54 -04:00
Daniel Mills aa51048990 Fixes 2021-07-19 06:32:44 -04:00
Daniel Mills 6d117f824d Fix threading & other issues 2021-07-19 06:32:38 -04:00
Daniel Mills 0a591e748f Fix rendering issues 2021-07-19 06:32:27 -04:00
Daniel Mills be2cb5d2f2 Fix pregen thread counts 2021-07-19 06:32:20 -04:00
Daniel Mills 280ec22348 Better mca iteration 2021-07-19 06:32:13 -04:00
Daniel Mills f9a815bba6 Dont try to load mca regions 2021-07-19 06:31:57 -04:00
CocoTheOwner 1534dcc932 Rework according to discussed changes 2021-07-19 12:07:29 +02:00
Daniel Mills e19ae7af59 Math fixes 2021-07-19 05:43:46 -04:00
Daniel Mills 1726d6f59e Iterator event fixes 2021-07-19 05:41:30 -04:00
Daniel Mills 756f395158 Method api & impl changes 2021-07-19 05:41:19 -04:00
Daniel Mills 90d2cf4970 Pregen job tweaks 2021-07-19 05:41:10 -04:00
Daniel Mills 37e70fd5bb fix pregen command to use mca coords 2021-07-19 05:41:01 -04:00
Daniel Mills 126481833b Sync get future 2021-07-19 05:40:48 -04:00
Daniel Mills b2630cfb49 Pos tostring 2021-07-19 05:40:43 -04:00
CocoTheOwner 579e2e95e3 Remove old, shove in new. 2021-07-19 11:07:24 +02:00
Daniel Mills 6580436900 Async pregen start 2021-07-19 04:37:06 -04:00
Daniel Mills a0ecf72a1c Open gui on pregen start 2021-07-19 04:34:34 -04:00
Daniel Mills bc3aaa94de Actual command tests 2021-07-19 04:33:14 -04:00
Daniel Mills 482ca5ed15 Command tests 2021-07-19 04:31:06 -04:00
Daniel Mills 66894c2e8a Pregen utils for commands 2021-07-19 04:30:59 -04:00
Daniel Mills 5d5a1fadd9 The Iris Pregenerator (job) 2021-07-19 04:22:33 -04:00
Daniel Mills 2d531c3a15 The Iris pregenerator (logic) 2021-07-19 04:22:24 -04:00
Daniel Mills e4da11727a Pregen methods impl 2021-07-19 04:22:14 -04:00
Daniel Mills 309f97b443 Pregen method api 2021-07-19 04:22:03 -04:00
Daniel Mills 98240e910e Pregen tasks 2021-07-19 04:21:54 -04:00
Daniel Mills 6d79d3d1f8 Support pregen listeners in iris comp gen 2021-07-19 04:21:49 -04:00
Daniel Mills def4e19359 Tweaks to headless gens 2021-07-19 04:21:33 -04:00
Daniel Mills 6cfd055bec Pos utils 2021-07-19 04:21:22 -04:00
Daniel Mills 1208b25b66 Move around guis 2021-07-19 02:00:15 -04:00
Daniel Mills b25bf70224 ChunkPosition -> Position2 2021-07-19 02:00:07 -04:00
Daniel Mills 7ecb139dba CMP 2021-07-19 01:47:00 -04:00
Daniel Mills 2a7720852a Move components 2021-07-19 01:46:54 -04:00
Daniel Mills 627eb556cb The headless generator 2021-07-19 01:45:29 -04:00
Daniel Mills e251155f4f NBT Force flushing 2021-07-19 01:45:21 -04:00
Daniel Mills 4e9c44e574 V+ 2021-07-19 01:45:09 -04:00
CocoTheOwner 5b662df7cd ifs 2021-07-19 02:09:52 +02:00
Dan 802574fb44 Merge pull request #433 from VolmitSoftware/headless
Iris Headless Support
2021-07-18 19:50:35 -04:00
Daniel Mills 51f3334711 Headless generation 2021-07-18 19:49:54 -04:00
Daniel Mills 254b5c963b Headless Generator 2021-07-18 19:36:18 -04:00
Daniel Mills d5f64a79b2 Headless worlds 2021-07-18 19:36:02 -04:00
Daniel Mills dd54a714cb Fix init errors 2021-07-18 19:35:56 -04:00
CocoTheOwner d40e9e3d73 Add todo cards 2021-07-19 01:31:09 +02:00
Daniel Mills d5066adb4b Fix terrain chunk creation 2021-07-18 19:05:22 -04:00
Daniel Mills f933aae562 Cleanup imports 2021-07-18 19:02:26 -04:00
Daniel Mills db65a80a7f Drop support for non-world terrain chunks 2021-07-18 19:01:53 -04:00
Daniel Mills 2f9658fa22 No More Fake News 2021-07-18 19:00:45 -04:00
Daniel Mills ab06365a81 Fix FakeEngine 2021-07-18 18:59:48 -04:00
Daniel Mills 2bc27bd6f5 Ravine mod support headless 2021-07-18 18:59:14 -04:00
Daniel Mills 078ac1abe4 Post mod support headless 2021-07-18 18:59:01 -04:00
Daniel Mills 05259eb0d2 Deposit mod support headless 2021-07-18 18:58:49 -04:00
Daniel Mills 8c272ea032 Cave mod headless support 2021-07-18 18:58:36 -04:00
Daniel Mills f58f29389c Use IrisWorld instead of FakeWorld om World Creator 2021-07-18 18:58:16 -04:00
Daniel Mills 854b137820 Fix wrong list for worlds 2021-07-18 18:57:05 -04:00
Daniel Mills fb8b5ac6b6 Compound headless mode 2021-07-18 18:56:05 -04:00
Daniel Mills 35106f5e67 Engine support headless 2021-07-18 18:55:40 -04:00
Daniel Mills a5ea58dbf7 Terrain actuator headless 2021-07-18 18:55:25 -04:00
Daniel Mills b4ddc8e5fc Island experiment support headless 2021-07-18 18:54:55 -04:00
Daniel Mills 2223405e79 Decorant support headless 2021-07-18 18:54:42 -04:00
Daniel Mills 7260e0d0f5 Biome actuator headless 2021-07-18 18:50:53 -04:00
Daniel Mills 44cf7f7330 Iris Project headless 2021-07-18 18:50:33 -04:00
Daniel Mills 465ceab271 Pregen fix but it needs to be recreated 2021-07-18 18:50:07 -04:00
Daniel Mills fa818d50d1 Vision headless support 2021-07-18 18:48:57 -04:00
Daniel Mills deb75df77a Cleanup unused 2021-07-18 18:45:41 -04:00
Daniel Mills aae47e103b Spawn loc headless 2021-07-18 18:44:52 -04:00
Daniel Mills 957dbecb3d Plxmgr headless 2021-07-18 18:43:14 -04:00
Daniel Mills 75fe59ef79 Patch complex 2021-07-18 18:42:32 -04:00
Daniel Mills 63ba5cf39d Patch engine components 2021-07-18 18:42:05 -04:00
Daniel Mills 8a0b7fbc47 Patch goto in headless mode 2021-07-18 18:41:39 -04:00
Daniel Mills ea9fb5bcb0 Update Engine for Iris Worlds 2021-07-18 18:39:54 -04:00
Daniel Mills 21d8ceacf0 Initial spawn fixes 2021-07-18 18:39:36 -04:00
Daniel Mills 986cf025a0 Seed access for spawns 2021-07-18 18:38:47 -04:00
Daniel Mills 85b936ead2 Engine target fix for world folder 2021-07-18 18:38:12 -04:00
Daniel Mills f241679087 Fix world method in studio close 2021-07-18 18:37:55 -04:00
Daniel Mills 6618cdc0ad Composite generator use only iris worlds 2021-07-18 18:37:17 -04:00
Daniel Mills fe2de5d8ae Use Iris worlds 2021-07-18 18:37:09 -04:00
Daniel Mills bd954e7292 Support additional world methods 2021-07-18 18:36:59 -04:00
Daniel Mills 86367298cc Support get players 2021-07-18 18:25:46 -04:00
Daniel Mills 08346a2326 V+ 2021-07-18 18:23:20 -04:00
Daniel Mills 1bb9090434 Update engine compounds for iris worlds 2021-07-18 18:22:51 -04:00
Daniel Mills 879bcdf8d8 Update Engine Target to use Iris Worlds 2021-07-18 18:21:41 -04:00
Daniel Mills 1b7658d8f9 Iris Worlds 2021-07-18 18:21:04 -04:00
Daniel Mills d635705449 Fix pregen speed issue 2021-07-18 17:56:56 -04:00
CocoTheOwner af1b17cb98 Rename & rewrite, add limits & checks, better delete 2021-07-18 18:00:10 +02:00
CocoTheOwner 944ef83805 everything reworked 2021-07-18 17:21:29 +02:00
CocoTheOwner 20b3e95b4f Merge remote-tracking branch 'upstream/master' into saplingOverrides 2021-07-18 11:10:42 +02:00
Daniel Mills c2de0dd613 V+ 2021-07-18 04:47:31 -04:00
Daniel Mills 2a7f5f55b0 Fix failed to gen on really anything fixes #424 2021-07-18 04:47:11 -04:00
Daniel Mills a03539ad8b Speed up plax mgr 2021-07-18 04:46:55 -04:00
Daniel Mills c420a8a686 NBTWorlds! 2021-07-18 04:46:33 -04:00
CocoTheOwner 8d6a2e8882 Basic sapling manager logic. Needs polishing 2021-07-17 21:41:30 +02:00
CocoTheOwner 8da9156bb5 Saplings 2021-07-17 20:38:12 +02:00
CocoTheOwner ffc8a5188d Descriptions 2021-07-17 20:38:05 +02:00
CocoTheOwner 4eb1117659 Ok? 2021-07-17 18:45:11 +02:00
CocoTheOwner b4602c1e48 Create IrisSapling, Make sapling lists, rename toggles, add objects 2021-07-17 18:42:30 +02:00
CocoTheOwner 80ede42191 Remove toggle from biome/region sapling config & prevent non-iris world 2021-07-17 18:01:11 +02:00
CocoTheOwner ea6937c0ac Add sapling configs to biome and dimension files 2021-07-17 17:54:38 +02:00
CocoTheOwner 2f99ba8526 Merge remote-tracking branch 'origin/saplingOverrides' into saplingOverrides 2021-07-17 17:51:02 +02:00
CocoTheOwner 16324e16c7 Initial commit.
Captures block place and sapling grow events and prints info to the console. Initial configuration options are in place, definitions will be added.
2021-07-17 17:50:45 +02:00
Daniel Mills c2e1ecf5b1 V+ 2021-07-17 07:52:26 -04:00
Daniel Mills cd353217d6 Fix CMOD 2021-07-17 07:45:48 -04:00
Daniel Mills 6903f67f0f Attempt to patch mca holes 2021-07-17 07:36:15 -04:00
Daniel Mills 487dab793a Performance Improvements 2021-07-17 03:20:10 -04:00
StrangeOne101 4cd3f9bd38 Merge pull request #423 from StrangeOne101/master
Tweaks
2021-07-17 17:29:20 +12:00
StrangeOne101 f581e76057 Tweaks
- Tweaked cards in map neater around the text
- Fixed map coordinate inaccuracy when zoomed
- Fixed too many commas when teleporting via the map
- Fixed no message being sent to the player at all when a studio world is opened
- Increased map zoom default to 400% 
- Fixed create command NPE when downloaded a fresh pack
2021-07-17 17:26:12 +12:00
Daniel Mills 312aa856ad Storepage 2021-07-17 01:00:09 -04:00
Daniel Mills 6f4363e95b Cleanup 2021-07-17 00:36:16 -04:00
Daniel Mills 595ba92f06 Handle terrain mode rendering 2021-07-17 00:33:45 -04:00
Daniel Mills df9114d7b0 Parallax Feature Performance Caching 2021-07-17 00:33:32 -04:00
Daniel Mills 1496d0e482 Stream api additions 2021-07-17 00:30:02 -04:00
Daniel Mills 92f5783f35 Stop tracking cache hit ratios 2021-07-17 00:29:47 -04:00
Daniel Mills da1895125a Island terrain mode (experimental) 2021-07-17 00:29:33 -04:00
Daniel Mills bcdd470567 Improve biome generator performance (sync) efficiency 2021-07-17 00:29:09 -04:00
Daniel Mills 7cd0070d90 Cancel noise boxes for now... later 2021-07-17 00:28:37 -04:00
CocoTheOwner 8217949fa2 Initial commit.
Captures block place and sapling grow events and prints info to the console. Initial configuration options are in place, definitions will be added.
2021-07-16 17:28:28 +02:00
Daniel Mills 4bdf1553c9 f 2021-07-16 08:54:29 -04:00
Daniel Mills 6fd5d38cd8 Fixes 2021-07-16 07:30:20 -04:00
Daniel Mills 8a737104ef Dont pregen created worlds unless they are specified to 2021-07-16 07:28:25 -04:00
Daniel Mills a608da759b Fixes 2021-07-16 07:10:54 -04:00
Daniel Mills c828625cc1 Pregenerator tweaks 2021-07-16 06:34:28 -04:00
Daniel Mills 7ead247fcb Formatting 2021-07-16 06:29:45 -04:00
Daniel Mills 7a62af78d0 Proper warnings 2021-07-16 06:27:25 -04:00
Daniel Mills 9896d6e6b2 Fix 2021-07-16 05:49:16 -04:00
Daniel Mills 9c376183e0 Stop blowing up the VM when looking for biomes 2021-07-16 05:00:00 -04:00
Daniel Mills e5cc9a3cbf VolmitSender 2021-07-16 04:59:51 -04:00
Daniel Mills 985979ab14 No goto - tp 2021-07-16 04:03:09 -04:00
Daniel Mills 377a4c2284 Noise boxing 2021-07-16 03:54:09 -04:00
Daniel Mills 24242db981 V+ 2021-07-16 02:41:27 -04:00
Daniel Mills 73a4152b5d RF 2021-07-16 02:40:56 -04:00
Daniel Mills b5eb094566 Performance Improvements 2021-07-16 02:39:37 -04:00
Daniel Mills 71140381e0 Cleanup 2021-07-16 02:25:35 -04:00
Daniel Mills 22387b2610 Refs 2021-07-16 02:11:41 -04:00
Daniel Mills da53a7d469 Repackage utils 2021-07-16 02:11:37 -04:00
Daniel Mills b9b30f9f53 Refactors 2021-07-16 01:46:32 -04:00
Daniel Mills c984eb9b8c Restructure Packages (read commit description)
There are now three root packages
- Engine: Generator & JSON Scaffolding
- Core: Managers, Commands, NMS
- Util: Random utility packages
2021-07-16 01:46:22 -04:00
Daniel Mills eef548f6a1 Fix Rarity, Strings do not implement IRare 2021-07-16 01:39:28 -04:00
Daniel Mills 2da8ffb8cd Copper 2021-07-16 00:53:08 -04:00
Daniel Mills 8188c21641 Fixes 2021-07-16 00:52:21 -04:00
Daniel Mills 19dca8c5bb Fix gen crash 2021-07-16 00:50:58 -04:00
Daniel Mills 5de5b6047a Better generating 2021-07-16 00:50:53 -04:00
Daniel Mills 695c4159dc Hotload datapacks on hotload (still needs restart) 2021-07-16 00:15:13 -04:00
Daniel Mills 2624315bd5 Custom biome particles & spawns 2021-07-16 00:09:25 -04:00
Daniel Mills fff6e50702 Map changes 2021-07-15 23:25:06 -04:00
Daniel Mills 753b69726a Mapping overload 2021-07-15 07:58:20 -04:00
Daniel Mills df273aca7e Map madness 2021-07-15 03:38:27 -04:00
Daniel Mills 9e9feb5980 Engine support max densities 2021-07-15 03:38:22 -04:00
Daniel Mills e71eb465fb Use new map only 2021-07-15 02:01:49 -04:00
Daniel Mills e9f2550a26 It compiles 2021-07-15 02:00:53 -04:00
StrangeOne101 12bfd98011 Merge pull request #418 from StrangeOne101/map-1.17
New Studio Map Command
2021-07-15 17:57:57 +12:00
StrangeOne101 2b45056842 Merge branch 'master' into map-1.17 2021-07-15 17:56:57 +12:00
StrangeOne101 c86fdf07db Merge branch 'master' into map-1.17 2021-07-15 17:54:23 +12:00
Daniel Mills 385ffd5b27 Destroy everything 2021-07-15 01:53:35 -04:00
StrangeOne101 ad6b746124 New Studio Map Command
- Allows you to see a map of a world/pack (without being in the world)
- Chooses a random color for each biome if no color is chosen, based on vanilla derivative 

Dev note: 
- The biome at your cursor is not accurate when zoomed out
2021-07-15 17:52:20 +12:00
Daniel Mills 57e8b59db6 std tp alias for tps 2021-07-15 00:41:28 -04:00
Daniel Mills ecb869695e Performance Improvements Pregenerator 2021-07-15 00:39:08 -04:00
Daniel Mills c99c7ee6c9 Its faster to be sync when you're already mutlicore... 2021-07-15 00:38:57 -04:00
Daniel Mills d3c47fe1a4 Performance Improvements 2021-07-14 19:06:05 -04:00
Daniel Mills 1c21b23e8e Fix placeobjects not working fixes #409 2021-07-14 18:31:29 -04:00
Daniel Mills bb55cd3c42 Command goto g only 2021-07-14 17:05:05 -04:00
Dan 4a74190241 Merge pull request #411 from StrangeOne101/master
Attempted fix biome & region rarity
2021-07-14 17:03:54 -04:00
Dan 586f9594fb Merge pull request #415 from CocoTheOwner/master
Remove "g" from cmd alt list for gen explorer since also on goto
2021-07-14 17:02:40 -04:00
Daniel Mills e68facfd51 Fixes 2021-07-14 16:44:15 -04:00
Daniel Mills 7d423fa49d Totally stable optimizations that wont cause any problems at all. 2021-07-14 16:40:20 -04:00
Daniel Mills 886553a923 Cleanup / Update sources 2021-07-14 15:40:01 -04:00
Daniel Mills ff4a00d103 Support interpolator types for object scale ups 2021-07-14 15:28:29 -04:00
Daniel Mills dbdf8a97ae MCA Data Pack Support for custom biomes 2021-07-14 15:23:04 -04:00
Daniel Mills 2433e31db0 Faster MCA 2021-07-14 15:22:53 -04:00
Daniel Mills 5538aac732 Actually fix 2021-07-14 14:44:32 -04:00
Daniel Mills c05f169d54 Read server properties file for proper dpacks folder 2021-07-14 14:40:43 -04:00
Daniel Mills 24aaefe342 Wipe datapacks on setup 2021-07-14 14:06:40 -04:00
Daniel Mills c167e881fb Biomes support multi-custom instances 2021-07-14 13:51:01 -04:00
CocoTheOwner 9a8c22f487 Add le me to gradle 2021-07-14 19:49:20 +02:00
Daniel Mills fd8077a5cd Hi-lo biome exceptions now throw 2021-07-14 13:28:10 -04:00
Daniel Mills 6dd196a7a6 Schemadata descriptions for new enums 2021-07-14 13:25:35 -04:00
Daniel Mills bdfe14e278 Fix slab cmod op 2021-07-14 13:24:20 -04:00
Daniel Mills 1c7b318c3f Fix color parsing 2021-07-14 13:24:14 -04:00
Daniel Mills a6acc30eea Actually support nested json files in folders up to 2 subfolders 2021-07-14 12:59:36 -04:00
StrangeOne101 c54a849243 Optimizations 2021-07-15 00:48:27 +12:00
StrangeOne101 1f2871f649 Map Command Overhaul in 1.17
- Features biomes using random color (based on vanilla derivative) if a color isn't provided in the biome class
- Allows a map to be opened for packs that don't have a world open (`/iris std map overworld`)
2021-07-14 23:01:09 +12:00
StrangeOne101 147d5902ed Attempted fix biome & region rarity
- Attempted to fix the issue of biome & region rarity issue
2021-07-14 21:23:02 +12:00
Daniel Mills ec47ca1983 THE SUPER DUPER COOL BUT TOTALLY NOT STABLE THING THATS NOT READY YET... 2021-07-14 04:51:50 -04:00
Daniel Mills 6ca6fc6989 Cleanup SRC 2021-07-14 02:34:13 -04:00
Dan f29015426f Merge pull request #410 from CocoTheOwner/jigsawexit
add command alts
2021-07-14 02:31:50 -04:00
Daniel Mills 57f09fd64d Trilinear, Tricubic & Trihermite 2021-07-14 02:31:15 -04:00
Daniel Mills 2477ee4952 Support Object scaling 2021-07-14 02:31:06 -04:00
Daniel Mills 2bd7fd2aa7 Support iris object scaling 2021-07-14 02:30:15 -04:00
Daniel Mills bc5529ded8 NMS for datapack discovery 2021-07-14 02:29:58 -04:00
Daniel Mills b05736e4fb What biome support datapack biomes 2021-07-14 02:29:47 -04:00
Daniel Mills 6a187881c0 Drop EWG Object conversion 2021-07-14 02:29:38 -04:00
Daniel Mills d1accc4e09 ffff 2021-07-13 19:04:05 -04:00
Daniel Mills e7e130460e Auto stash before revert of "Flush " 2021-07-13 18:55:51 -04:00
Daniel Mills d33a02f6cd Revert "Flush "
This reverts commit 8f6c511598.
2021-07-13 18:14:35 -04:00
Daniel Mills 8f6c511598 Flush 2021-07-13 18:14:27 -04:00
CocoTheOwner 7fb04d51ae add command alts 2021-07-13 02:25:16 +02:00
Daniel Mills 64d5275554 V+ 2021-07-11 10:23:09 -04:00
Daniel Mills 76041a9f55 Fix decorator issues 2021-07-10 23:58:59 -04:00
Daniel Mills 502f7de040 Fix OOB exceptions for ceiling decorators 2021-07-10 23:37:29 -04:00
Daniel Mills a86512e807 Skip invalid height 2021-07-10 23:37:16 -04:00
Daniel Mills 2e19dbe05f Fixes + Nowhere 2021-07-10 23:23:39 -04:00
Dan 18d8f07242 Merge pull request #408 from StrangeOne101/1.17
Decorator Updates
2021-07-10 23:20:59 -04:00
StrangeOne101 fb0ff734d7 You didn't see anything... 2021-07-10 22:49:25 +12:00
StrangeOne101 13a5482f91 Added Top Threshold option to decorators
- Added topThreshold option to decorators. Allows stacked blocks to start using the top palette for x percent of the stack
2021-07-10 17:33:32 +12:00
StrangeOne101 4a5794ad5f Fixed weight not being used in decorator palettes 2021-07-10 14:26:33 +12:00
StrangeOne101 7652aca406 Decorator Updates and Fixes - Season 2
- Added SEA_FLOOR type to decorators which will only place bellow sea level/in water
- Implemented CEILING type to decorators, which will allow stacked blocks to place from the top down
- Fixed height variance not being implemented properly. The height of stacked blocks is now correct

Dev notes: This WILL require updates to the overworld pack as fixing some of the bugs here made some workarounds in the overworld pack kinda break
2021-07-10 03:34:18 +12:00
StrangeOne101 f1dfb1c952 Decor Updates
- Fixed decorators using the same RNG when picking from a pool of blocks as it used to place it. This caused it to only use pool entries above the placement chance threshold. (E.g. a flower pool with a 0.15 chance to place will only use the first 0.15 of the pool of flowers since the RNG is the same)
- Removed `zoom`, `verticalZoom` and `varianceZoom` properties from decorators. Zooms can be done inside the individual styles instead.
- WIP Tab Completion (Create command only rn)

Dev notes:
It's very important from now on that in decorators, the X and Z remain switched when getting noise so that the result of the noise is not exactly the same as the noise that choose if the decor should be placed or not
2021-07-10 03:34:18 +12:00
Daniel Mills b4f866be03 Fixes 2021-07-09 08:00:38 -04:00
Daniel Mills 4fed35d189 V+ 2021-07-08 11:18:57 -04:00
Daniel Mills c3cff6a911 Fixes 2021-07-08 05:20:20 -04:00
Daniel Mills 91f718f39d Cache tweaks 2021-07-08 04:32:39 -04:00
Dan e4ea01cfd2 Merge pull request #406 from CocoTheOwner/jigsawexit
Create a jigsaw exit command
2021-07-08 04:31:51 -04:00
CocoTheOwner dd7d5a46e8 Create a jigsaw exit command 2021-07-08 10:25:40 +02:00
Daniel Mills 3be4b08bb0 Test paper deadlock fix 2021-07-07 19:22:36 -04:00
Daniel Mills 2fde7b7879 V+ 2021-07-07 05:36:34 -04:00
Daniel Mills 60777e5714 Utils 2021-07-07 05:35:57 -04:00
Daniel Mills 9d7fcc4987 Drop NMS Binding for fastworlds 2021-07-07 05:35:38 -04:00
Daniel Mills 95ed1c3ac3 Profiler 2021-07-07 05:35:28 -04:00
Daniel Mills 7e285969a7 Ignore rotation issues 2021-07-07 03:22:22 -04:00
DanLT 083f1be766 Cleanup parallax manager 2021-07-06 06:43:34 -08:00
DanLT d39ed3c03f Support "async" spawning 2021-07-06 06:43:24 -08:00
DanLT a068bda34f File collecting
its not efficient
2021-07-06 06:43:01 -08:00
DanLT 559f37e5d2 Revert "Revert "Revert "Test carved objects"""
This reverts commit 6df6296cd1.
2021-07-06 02:31:54 -08:00
DanLT d50b7de668 Revert "Dont check caves"
This reverts commit 35c5191a4d.
2021-07-06 02:31:51 -08:00
DanLT 35c5191a4d Dont check caves 2021-07-06 02:21:31 -08:00
DanLT 6df6296cd1 Revert "Revert "Test carved objects""
This reverts commit b88a565344.
2021-07-06 02:20:52 -08:00
DanLT b88a565344 Revert "Test carved objects"
This reverts commit d98bc1ec63.
2021-07-06 02:20:15 -08:00
DanLT d98bc1ec63 Test carved objects 2021-07-06 02:04:39 -08:00
Dan 74886db10d Merge pull request #401 from StrangeOne101/1.17
Fixed Strongholds not generating in 1.17
2021-07-06 05:43:59 -04:00
StrangeOne101 48e4fd8088 Fixed Strongholds not generating in 1.17
- Fixed Strongholds not generating in 1.17
- Made 9 strongholds now generate by default

Dev note: Tile entities are not placing correctly in jigsaws when generating (manual placement is fine)
2021-07-06 20:28:56 +12:00
DanLT 54c7f896ad Dont optimize chunks in mca anymore (for now) 2021-07-06 00:18:32 -08:00
Daniel Mills ccd14e6505 f 2021-07-05 06:10:31 -04:00
Daniel Mills dc33534478 Psychotic builds 2021-07-05 06:02:37 -04:00
Daniel Mills 83d79e23d5 Focus mode is back! 2021-07-05 06:01:29 -04:00
Daniel Mills 151f99e343 Temp fix for crashes on rotation issues 2021-07-05 06:01:22 -04:00
Daniel Mills ccea63f1e2 V+ 2021-07-05 05:09:46 -04:00
Daniel Mills 1019419d85 Add beautify json command (bulk project) 2021-07-05 05:09:39 -04:00
Daniel Mills a234823d0a Allow json objects to retain order of loaded objects 2021-07-05 05:09:26 -04:00
Daniel Mills d2015845e8 Fixes and tweaks 2021-07-04 23:40:46 -04:00
Daniel Mills 1857cca081 Fix gradle versioning issue (build cache) 2021-07-04 22:31:15 -04:00
Daniel Mills 195bbf4652 Fix poss null 2021-07-04 22:26:00 -04:00
Daniel Mills af76d937e1 Or not 2021-07-04 22:22:42 -04:00
Daniel Mills 885780a92b Stop using paper rapid start worlds (deadlocking) 2021-07-04 22:15:58 -04:00
Daniel Mills 547bfe26d7 Fix rotation cmods (bukkit logic is causing issues in switches) 2021-07-04 22:15:38 -04:00
Dan bc87fdde1f Merge pull request #392 from CocoTheOwner/no-day-weather-studio
Update IrisProject.java
2021-07-04 21:55:14 -04:00
Dan 6fc8324337 Merge pull request #399 from StrangeOne101/1.17
Fixed Loot tables acting like a cucumber in the sun
2021-07-04 21:54:27 -04:00
StrangeOne101 5103d91db0 Fixed Loot tables acting like a cucumber in the sun
- Fixed loot chests containing the exact same loot
- Fixed chests having nothing if a roll fails
- Fixed rolls always being done in the same order
- Added method to BlockPosition to get a unique long from 3 integers (x, y z)
2021-07-05 04:24:13 +12:00
CocoTheOwner b67ab5ecd3 You got it 2021-07-04 10:03:41 +02:00
Dan 3aa085f17e Merge pull request #390 from CocoTheOwner/add-alt-cmd
Update CommandIrisObjectSave.java
2021-07-04 03:54:38 -04:00
Dan 1b9a78827a Merge pull request #398 from StrangeOne101/1.17
Revert stackoverflow, persistent entities in jigsaws
2021-07-04 03:52:55 -04:00
StrangeOne101 3299e0fa6a Re-implemented lost object placement heightmap 2021-07-04 16:32:04 +12:00
StrangeOne101 79e12c8dd8 Incremented Version 2021-07-04 14:01:29 +12:00
StrangeOne101 ced33114cc Jigsaw Enhancements
- Added keepEntity property for jigsaw connectors. When set to true, mobs spawned will not despawn
- Fixed jigsaw entities being spawned off-center
2021-07-04 14:01:01 +12:00
Daniel Mills 294324e31b Revert "JSON Tweaks "
This reverts commit 9ddf09e187.
2021-07-04 14:00:12 +12:00
StrangeOne101 1b27d652af Merge remote-tracking branch 'upstream/master' into 1.17 2021-07-04 13:57:52 +12:00
Daniel Mills e98209249f V+ 2021-07-03 01:57:32 -04:00
Daniel Mills 9ddf09e187 JSON Tweaks 2021-07-03 01:49:55 -04:00
Daniel Mills 73e975cfa1 BP Hash 2021-07-03 01:49:48 -04:00
Daniel Mills a942ea6ee2 My god gradle is slow 2021-07-03 01:42:52 -04:00
Daniel Mills 38ca7c030f Fix init compound update world 2021-07-03 01:21:50 -04:00
StrangeOne101 7d59edc8a5 Added Object & Jigsaw Loot Tables
- Added the ability for placed objects and jigsaws to have select loot tables
    - Includes the ability to filter loot tables based on block type/block state
    - Overrides loot tables from the biome/region, but if no loot tables are provided for an object, they will still be used
    - Uses weight based system for determining which table to pick (so it's guaranteed rather than by chance)
- Added WeightedRandom util class
- Fixed loot tables not being random based on the seed
- Fixed jigsaws breaking bedrock
- Fixed enchantments in loot tables not working for enchanted books
- Fixed mobs spawned in Jigsaws not being spawned in the center like they should be
2021-06-29 00:24:36 +12:00
CocoTheOwner 7d05450244 Update IrisProject.java 2021-06-27 13:58:59 +02:00
CocoTheOwner 8e5ba70706 typo 2021-06-27 02:33:19 +02:00
CocoTheOwner d43c40fc59 jigsaw shortcuts 2021-06-27 02:32:49 +02:00
CocoTheOwner 02ce707546 Update CommandIrisObjectSave.java 2021-06-27 02:29:07 +02:00
DanLT 4a11ed6dc4 Stop blinding people 2021-06-24 18:11:56 -08:00
DanLT 72d07fc3fb JFC paper chill it on the patches 2021-06-24 18:11:45 -08:00
DanLT 9b910b26a9 Support Objects in Regions 2021-06-24 08:32:13 -08:00
DanLT 4a9be7b4a5 Tweak binding size 2021-06-23 21:26:37 -08:00
DanLT 34958c654f Update compound worlds on the fly 2021-06-23 21:14:41 -08:00
DanLT bf91156182 Iris Create / Project Load/Create skipLoadSpawn 2021-06-23 21:14:29 -08:00
DanLT b774b6920d NMS 1.17 skip loadSpawn 2021-06-23 21:13:51 -08:00
DanLT 56f6143873 Pregenerator force override gui option 2021-06-23 21:13:41 -08:00
DanLT 7ba051c7ff Hotswapping of worlds 2021-06-23 21:13:27 -08:00
DanLT 63f5c240a4 Support engine targets to hotswap worlds 2021-06-23 21:13:17 -08:00
DanLT ba91e433c3 Fix jigsaw scaffold error (temp) 2021-06-23 21:13:08 -08:00
DanLT 2818b207db World creator height supports 2021-06-23 21:12:53 -08:00
DanLT 0527066a4c Fake world improvements 2021-06-23 21:12:45 -08:00
DanLT a48400535f tweaks 2021-06-23 20:09:33 -08:00
DanLT fa201d21e2 fixes 2021-06-23 07:05:05 -08:00
DanLT c9222ff6f6 Fix console spam 1.17+ 2021-06-22 13:45:37 -08:00
DanLT a45d311339 Better caching 2021-06-22 09:35:28 -08:00
DanLT b59278601c tc init 2021-06-22 09:26:00 -08:00
DanLT b48a913fbf Update new mapping classes for nms 1.17 2021-06-22 09:18:27 -08:00
DanLT a0ef779aee Biome IDs for 1.17 nms 2021-06-22 09:14:03 -08:00
DanLT 861ff636b1 NMS Bindings for 1.17 only 2021-06-22 09:09:19 -08:00
DanLT bbce4bdeb0 INMS Register new binding for 1.17 2021-06-22 09:09:06 -08:00
DanLT df0ceb4850 V+ 2021-06-22 09:08:53 -08:00
DanLT b1e45756dd Remove 1.16 NMS Bindings 2021-06-22 09:08:48 -08:00
DanLT 4b52878dc1 Update Gradlew 2021-06-22 09:03:42 -08:00
DanLT 62a4f18866 Fix gradle 2021-06-22 09:03:08 -08:00
DanLT b00faf3930 Merge branch 'pr/385' 2021-06-22 09:02:28 -08:00
StrangeOne101 d6ddb2b247 Fixes Season 2
- Fixed double sided connectors not working properly
- Fixed skulls not being able to be rotated when they are placed on an angle that isn't perfectly straight (north, south, east, west)
2021-06-22 09:00:43 -08:00
StrangeOne101 900510e7f2 Reverted accuracy changes to noise generator
- Also fixed some overlooked issues
2021-06-22 09:00:08 -08:00
StrangeOne101 7370af0084 Re-added /iris std generator
Re-added the studio generator command. Fixed it up a bit (stopped random jitters that occurred)
2021-06-22 09:00:08 -08:00
StrangeOne101 9644693de3 More fixes
- Fixed Jigsaws not placing TileStates
- Fixed NPE when banners, signs or skulls try to be rotated to be up or down
2021-06-22 09:00:08 -08:00
StrangeOne101 b6bc269505 Fixes
- Fixed NPE occuring when Jigsaw fails to find Object in pool. Now just produces a warning in the console like it should
- Fixed pointless copies of lists being created within PlannedStructure
- Fixed mobs spawning within other blocks and suffocating on spawn
- Fixed creating a jigsaw with a non existent object causing jigsaw commands to lock up (#377)
- Fixed editing a non existent jigsaw just not doing anything
2021-06-22 09:00:08 -08:00
DanLT 82161c10f8 Revert "Gradle Updates for deps"
This reverts commit f2dd96c3bf.
2021-06-22 09:00:08 -08:00
DanLT c939033031 Revert "Update Wrapper for g7.1 (to support j16)"
This reverts commit 6d32fe743b.
2021-06-22 08:59:57 -08:00
DanLT 8c8c040fb3 Update the c 2021-06-21 17:34:43 -08:00
DanLT 48a07e2b34 deps 2021-06-21 09:13:12 -08:00
DanLT 7bdfbb17b5 Bug with spigot itself, syncs the get() in blockdata (toBukkit cmod) 2021-06-21 07:13:17 -08:00
DanLT 492646edc3 Remove debugging messages 2021-06-21 07:12:47 -08:00
DanLT 975096b221 Remove smoothiemap usages 2021-06-21 06:54:29 -08:00
DanLT 0c4c790a65 Remove smoothiemaps 2021-06-21 06:53:53 -08:00
Dan 012539b5d3 Update README.md 2021-06-17 19:38:28 -08:00
Dan f4cd9f27e4 Merge pull request #375 from VolmitSoftware/mc17j16
1.17
2021-06-17 19:37:30 -08:00
DanLT 449c2322b8 Height warnings 2021-06-17 19:35:07 -08:00
DanLT 7efdcdc592 linked terrain chunk min height support 2021-06-17 19:25:42 -08:00
DanLT 356aa27f70 Replace GRASS_PATH with DIRT_PATH 2021-06-17 19:22:40 -08:00
DanLT a579c7882f Get min/max height for regenerate method from composite 2021-06-17 19:22:08 -08:00
DanLT 3077cf6bb5 Support min height 2021-06-17 19:21:53 -08:00
Dan 7c46ee50a4 Merge pull request #376 from VolmitSoftware/master
Give me fixes
2021-06-17 19:18:07 -08:00
Dan 1d37910bd4 Merge pull request #369 from CocoTheOwner/Improve-progess-indicator-pregen
Update ProjectManager.java
2021-06-17 19:15:52 -08:00
Dan 5555ff2ac1 Merge pull request #371 from CocoTheOwner/prevent-parallaxmanager-npe
Update EngineParallaxManager.java
2021-06-17 19:15:23 -08:00
Dan 2ad93d79f5 Merge pull request #374 from StrangeOne101/master
Object NBT Support Additions
2021-06-17 19:14:22 -08:00
DanLT 6d32fe743b Update Wrapper for g7.1 (to support j16) 2021-06-17 19:11:23 -08:00
DanLT f2dd96c3bf Gradle Updates for deps 2021-06-17 19:11:07 -08:00
StrangeOne101 9b4384d3e6 Object NBT Support Additions
- Added support for banner NBT to be saved to objects
- Fixed spawners not saving their spawn type to objects
- Removed pointless IDs from the TileEntity registry system
2021-06-17 17:59:06 +12:00
CocoTheOwner c1831e58f5 oops, removed again, without gradle 2021-06-12 12:40:56 +02:00
CocoTheOwner 991f241209 Revert "ok, remove debug"
This reverts commit b565cf4be1.
2021-06-12 12:40:27 +02:00
CocoTheOwner b565cf4be1 ok, remove debug 2021-06-12 12:40:12 +02:00
Daniel Mills 8a5e1cd4f1 V+ 2021-06-11 16:53:23 -04:00
Daniel Mills 4445121023 Prep for async prepare 2021-06-11 16:53:13 -04:00
Daniel Mills 1421869094 Fix NPE on Multiverse core link 2021-06-11 16:52:53 -04:00
Daniel Mills 24480ef13a Prepare spawn async support 2021-06-11 16:52:43 -04:00
Daniel Mills 73bd7991bb Support async creation 2021-06-11 16:52:30 -04:00
Daniel Mills 6e74761977 Fake world support names 2021-06-11 16:52:18 -04:00
Dan a7f7a226d6 Merge pull request #370 from CocoTheOwner/fix-pregen-progressbar
Fix pregen progressbar
2021-06-11 16:51:18 -04:00
CocoTheOwner 491cdc5fef Update EngineParallaxManager.java 2021-06-11 09:54:51 +02:00
CocoTheOwner 98fe47d085 Undo previous calculation change. Move elapsed to start after drawing task for more accuracy. 2021-06-11 09:53:10 +02:00
CocoTheOwner 2290627c5f Update Pregenerator.java 2021-06-11 09:42:04 +02:00
CocoTheOwner 656d436028 Update ProjectManager.java 2021-06-11 09:39:10 +02:00
Daniel Mills cff8e822f2 V+ 2021-06-09 08:57:47 -04:00
Dan 5dfd46f809 Merge pull request #365 from CocoTheOwner/fixCustomField
Undo a change that breaks when Biome.CUSTOM is not a field on your MC Distribution
2021-06-09 08:47:03 -04:00
CocoTheOwner 97a6395ddd Update DirectWorldWriter.java 2021-06-08 21:08:04 +02:00
CocoTheOwner 5226106ff0 Undo a change that breaks when Biome.CUSTOM is not a field on your MC distribution 2021-06-08 16:35:01 +02:00
Daniel Mills 5e838e3f10 G+ 2021-06-08 06:57:28 -04:00
Daniel Mills 574860f117 Fix coadloads 2021-06-06 01:48:26 -04:00
Daniel Mills c19857d463 Fix buildscript 2021-06-06 01:13:47 -04:00
Daniel Mills 6741d58bec Convert Iris to Gradle 2021-06-06 00:24:43 -04:00
Daniel Mills eb806dbca5 Drop json stuff 2021-06-06 00:24:02 -04:00
Dan 7d8aa683ff Merge pull request #363 from CocoTheOwner/pregen-patch
V+ & patch pregen bug
2021-06-05 16:36:27 -04:00
CocoTheOwner 3bceaa7a2f V+ & patch pregen bug 2021-06-05 22:33:07 +02:00
Daniel Mills bfa45b66ac Update repos 2021-06-03 10:12:07 -04:00
Dan e39b89859f Merge pull request #354 from avixk/master
Villager Despawning Fix
2021-05-27 20:00:38 -04:00
Dan 1f14a01994 Merge pull request #360 from Mohamad82Bz/master
Removed two console debug messages that left by accident
2021-05-27 19:59:32 -04:00
Dan 91310ce98c Merge pull request #361 from CocoTheOwner/patch-1
Update LICENSE.md
2021-05-27 19:59:08 -04:00
CocoTheOwner 94dcfbf197 Update LICENSE.md 2021-05-27 21:43:43 +02:00
Mohamad82Bz d2b4b8b279 Removed two console debug messages that left by accident 2021-05-27 01:59:06 +04:30
Dan aaa9495ceb Merge pull request #357 from Mohamad82Bz/master
Running EnginePlayer Async  | Fixed DustRevealer was working with normal glowstones
2021-05-25 12:54:40 -04:00
Dan a8b75c9b13 Merge pull request #349 from pazkaI/master
Pregen fix: exclude custom biome from id mapping
2021-05-25 12:52:59 -04:00
Mohamad82Bz f21685e81d Removed J.sr (runTask) as it's no longer needed 2021-05-25 21:16:15 +04:30
Mohamad82Bz e24a7f887a Using scheduleSyncDelayedTask for sync parts 2021-05-25 21:15:51 +04:30
Mohamad82Bz 54b5ab6d79 Fixed PotionEffect was adding asynchronously 2021-05-25 20:24:06 +04:30
Mohamad82Bz 361b2ed7c7 EnginePlayer is now running async 2021-05-25 11:43:52 +04:30
Mohamad82Bz cee6c7c999 Reworked and fixed isDust and isWand check #2 2021-05-23 22:04:41 +04:30
Mohamad82Bz 3e5efd4090 Reworked and fixed isDust and isWand check 2021-05-23 21:51:59 +04:30
DanLT 10b8ba1ec8 V+ 2021-05-17 15:04:31 -08:00
avixk be90380775 Added a bandaid to keep villagers from despawning 2021-05-16 14:15:14 -05:00
avixk 3b2e775698 Added a bandaid to keep villagers from despawning 2021-05-16 14:13:58 -05:00
Pascal 01ba90689f compile for java 8 2021-05-09 20:13:25 +02:00
Dan e53fa82c46 Merge pull request #350 from pazkaI/villager-fix
fix villager despawning issue
2021-05-09 09:58:58 -08:00
DanLT 48ccbfd860 135 2021-05-09 08:20:14 -08:00
DanLT 601d4e1f48 Set all entities to persistent closes #348 2021-05-09 08:08:13 -08:00
Pascal 7fce3bdd25 fix villager despawning 2021-05-08 14:05:41 +02:00
Pascal 4b1bccd4b5 exclude custom biome from id mapping 2021-05-07 15:54:54 +02:00
Andrew Baker 28e809f92f Add message if couldn't find specific branch
Took 5 minutes
2021-04-29 11:37:51 -07:00
Andrew Baker 866e0e3fa9 Add branch support to listings
Took 1 hour 51 minutes
2021-04-29 11:23:31 -07:00
Andrew Baker ecaa388bbe V+ for release 2021-03-29 10:26:24 -07:00
Andrew 4cfdc5bf30 Merge pull request #327 from stijnb1234/master
General cleanup
2021-03-15 10:40:17 -07:00
Andrew Baker ed1090430a Re-add issue templates
Took 4 minutes
2021-03-15 10:39:12 -07:00
stijnb1234 3322cd73ea Readded packignore.ignore and added release/ to .gitignore 2021-03-15 17:35:16 +01:00
stijnb1234 824f6ac4aa Readded Iris.iml again 2021-03-15 17:22:39 +01:00
stijnb1234 d5dfa89fdd .gitignore fix 2021-03-15 17:21:48 +01:00
stijnb1234 9628a98568 Readded Iris.iml to resolve conflict 2021-03-15 17:21:29 +01:00
Andrew Baker cc3428d24b Cleanup
Took 1 minute
2021-03-15 09:16:12 -07:00
stijnb1234 fe684e9bbd .gitignore update 2021-03-15 17:15:46 +01:00
stijnb1234 f31ea9b7cf General cleanup 2021-03-15 17:15:25 +01:00
Andrew Baker 7eeebb66ab Remove gource 2021-03-15 09:11:06 -07:00
Andrew Baker e4f53c1daf Remove unused obfuscators 2021-03-15 09:08:39 -07:00
Andrew Baker 98bd58a92c Prepare for OS
Took 47 minutes
2021-03-13 13:09:57 -08:00
CocoTheOwner f9a7c12620 Update PlannedStructure.java 2021-03-10 16:20:30 +01:00
CocoTheOwner d04223e174 Revert "Update IrisJigsawPieceConnector.java"
This reverts commit e71c514ef6.
2021-03-10 16:13:29 +01:00
CocoTheOwner e71c514ef6 Update IrisJigsawPieceConnector.java 2021-03-10 16:13:22 +01:00
CocoTheOwner 57b089ac89 Add entityCount in jigsaw, add chance in object replace 2021-03-10 13:04:34 +01:00
CocoTheOwner b97ff6fe14 DevV+ 2021-03-10 13:04:08 +01:00
CocoTheOwner c57ac4c9fe Remove villager building cancelling check
This reintroduces villages but they can spawn in the water
2021-03-10 10:52:01 +01:00
CocoTheOwner 9cf60ca227 Add more command aliases 2021-03-10 10:51:40 +01:00
CocoTheOwner 8c9684e35c Remove blindness when failed studio create 2021-03-09 23:27:04 +01:00
CocoTheOwner 00854e2ea7 Reduce blindess duration 2021-03-09 23:26:40 +01:00
CocoTheOwner dcae312d2c DevV+ 2021-03-09 23:26:26 +01:00
CocoTheOwner 963a851382 Remove confusion effect when creating studio worlds 2021-03-09 20:54:48 +01:00
CocoTheOwner 1d233c5250 DevV+ 2021-03-09 20:54:38 +01:00
CocoTheOwner 34919ec961 Cleanup and prevent crash error 2021-03-09 20:36:05 +01:00
CocoTheOwner d15228656f Move potion effects so they dont trigger on accident 2021-03-09 20:34:57 +01:00
CocoTheOwner dd20a06f80 Make sure sender is player before applying effects 2021-03-09 15:21:25 +01:00
CocoTheOwner a4bbad3243 Prevent opening desktop when in headless mode 2021-03-09 15:21:09 +01:00
CocoTheOwner 343ae99b54 Warn for smoothie's illegal reflective access 2021-03-09 15:20:55 +01:00
CocoTheOwner 41d1714d28 Cleanup and prevent some potential NPEs 2021-03-09 13:41:38 +01:00
CocoTheOwner c20a2ecd9a Add 'd' to object dust command alias list 2021-03-09 10:30:56 +01:00
CocoTheOwner b609daf89d DevV+ 2021-03-09 09:56:25 +01:00
CocoTheOwner f1cdcdaf3e Move commands into folders 2021-03-09 09:56:21 +01:00
CocoTheOwner 01da323ddb Add effects to creating studio worlds 2021-03-08 23:06:14 +01:00
CocoTheOwner 9675a608a8 DevV+ 2021-03-08 22:24:39 +01:00
CocoTheOwner 8231f28ded Prevent NPE and fix stupid typo 2021-03-08 22:01:36 +01:00
CocoTheOwner 772ecea71a Update gen message 2021-03-08 20:48:44 +01:00
CocoTheOwner 63a74f2dc9 Prevent editbiome in non-studio world 2021-03-08 18:51:00 +01:00
CocoTheOwner 4f3286f520 Replace stacktrace spam with warning 2021-03-08 18:50:49 +01:00
CocoTheOwner 26ca025070 Requrienonnull and remove info 2021-03-08 18:50:39 +01:00
CocoTheOwner 49b1c553ba Min bore ex y to -1 2021-03-08 18:50:28 +01:00
CocoTheOwner 55948d5b79 Fix tooltip 2021-03-08 18:50:13 +01:00
CocoTheOwner 3aeee71873 RequireNonNull & ignore 2021-03-08 18:50:07 +01:00
CocoTheOwner d669e733cd Updating -> Building workspace 2021-03-08 18:49:54 +01:00
CocoTheOwner d9af34ee81 Non-negative blockdata getters 2021-03-08 18:49:45 +01:00
CocoTheOwner 8649dbda35 DevV+ 2021-03-08 18:49:26 +01:00
CocoTheOwner c7d05fbdb1 Remove cave modified. It's bad 2021-03-08 15:24:49 +01:00
CocoTheOwner f2cfd50a44 Patch cave gen enhancement 2021-03-08 15:14:26 +01:00
CocoTheOwner a8146a2e81 More information on recursive and other parallax object placement issues 2021-03-08 15:14:13 +01:00
CocoTheOwner d166cc2a4a Add desciption & cleanup 2021-03-07 12:16:35 +01:00
CocoTheOwner 4558cb4846 Modify message for hotloading 2021-03-07 12:13:43 +01:00
CocoTheOwner 143349be3a Add desc to dimension field 2021-03-07 12:13:19 +01:00
CocoTheOwner 82ac92dfed Remove biome specific fluid type 2021-03-07 11:01:58 +01:00
CocoTheOwner 2643eb2e37 Reduce studio world pregen size and increase update frequency 2021-03-07 11:01:41 +01:00
CocoTheOwner ac03b9f539 Set player gamemode to spectator when tping to studio 2021-03-07 11:01:24 +01:00
CocoTheOwner a4370ba55a Remove objects from iris goto 2021-03-07 11:01:11 +01:00
CocoTheOwner 7aa4a01355 DevV+ 2021-03-06 17:03:09 +01:00
CocoTheOwner 349a9de592 Add s to studio aliases 2021-03-06 16:48:48 +01:00
CocoTheOwner 58c0b45ac8 Update IrisCaveModifier.java 2021-03-06 16:40:08 +01:00
CocoTheOwner af0a923210 Patch 2021-03-06 16:39:59 +01:00
CocoTheOwner 9bcaeee794 Prevent caves from breaching ground in biomes with gen.min below sea 2021-03-06 16:39:06 +01:00
CocoTheOwner b6d8771792 Patch locate overwrite 2021-03-06 16:18:13 +01:00
CocoTheOwner f5fed50e4d DevV+ 2021-03-06 16:00:54 +01:00
CocoTheOwner 5a00f56a39 Disable regen command (again) 2021-03-06 15:32:13 +01:00
CocoTheOwner 0bb48f8af0 Warn when not specifying object with jig place 2021-03-06 15:04:16 +01:00
CocoTheOwner f3ceda1b3d Add entity spawn location setting
Also prevents entities from spawning when a building was not spawned
2021-03-06 14:54:28 +01:00
CocoTheOwner 24c4cbe901 DevV+ 2021-03-06 13:00:30 +01:00
CocoTheOwner 26e2b2507d Attempt getting sky biome, into ground, into derivative 2021-03-06 12:58:48 +01:00
CocoTheOwner d3378c638a Add small inc/decr to chance to prevent line artifacts 2021-03-06 12:58:30 +01:00
CocoTheOwner aa90274cc2 Prevent NPE with getting compound (may not exist) 2021-03-06 12:58:17 +01:00
CocoTheOwner d0a281c116 Fix getgroundbiome to get ground biome not sky 2021-03-06 12:58:05 +01:00
CocoTheOwner 4cbf192e74 Update Interpolated.java
Reimplement blockdata (drop 1.15 for this to not cause issues with that version, because it will...)
2021-03-06 11:18:52 +01:00
CocoTheOwner 1edafc40d0 Properly rethrow an NPE so it doesn't get stuck in labda 2021-02-28 18:09:45 +01:00
CocoTheOwner 6a4a25db40 remove debug prints & add warning, replace throwable with exception 2021-02-28 17:52:54 +01:00
CocoTheOwner e5ca66305c Send more info on startup (java version etc) 2021-02-28 16:34:18 +01:00
CocoTheOwner 3250566949 First step towards finding objects 2021-02-28 15:34:26 +01:00
CocoTheOwner 4ced362dd0 Comment out block data from Interpolated 2021-02-28 15:28:53 +01:00
CocoTheOwner 4b87ac2907 Prevent NPE in Data Manager 2021-02-28 15:27:35 +01:00
CocoTheOwner 44e1b8f159 See description
- Catch /locate to be rerouted to /ir std goto
- Add "/ir locate", also directly points to /ir std goto
2021-02-28 15:27:24 +01:00
CocoTheOwner 81b1f06eec Replace studio TP command to prevent overlap with goto 2021-02-28 15:23:33 +01:00
CocoTheOwner 441d36e2e3 Remove unused import 2021-02-28 15:22:49 +01:00
CocoTheOwner 7cba76b1e8 Fix typo 2021-02-27 18:19:46 +01:00
CocoTheOwner c3304a78fb Give tips when facing issue with downloading pack 2021-02-27 14:35:02 +01:00
CocoTheOwner e6113b4aa1 Add strongholds (POG) 2021-02-26 12:28:58 +01:00
CocoTheOwner 044c7f997d Typo 2021-02-26 12:13:40 +01:00
CocoTheOwner abfba739a8 Prevent NPE when getting Engine 2021-02-26 10:13:32 +01:00
CocoTheOwner cec67484e5 Prevent NPE when world already exists 2021-02-25 10:03:22 +01:00
CocoTheOwner aafed9b1b7 Prevent NPE by not checking hotloader when null 2021-02-25 09:46:55 +01:00
CocoTheOwner 552a87462a Revert "Revert "Merge branch 'master' of https://github.com/VolmitSoftware/IrisPlugin""
This reverts commit 4c8013e6d2.
2021-02-24 23:15:49 +01:00
CocoTheOwner 4c8013e6d2 Revert "Merge branch 'master' of https://github.com/VolmitSoftware/IrisPlugin"
This reverts commit 4f5efba173, reversing
changes made to e0c94af535.
2021-02-24 23:15:42 +01:00
CocoTheOwner 4f5efba173 Merge branch 'master' of https://github.com/VolmitSoftware/IrisPlugin 2021-02-24 23:15:17 +01:00
CocoTheOwner e0c94af535 V+ 2021-02-24 23:15:07 +01:00
Andrew a8ff0b5e62 Create LICENSE.md 2021-02-24 13:35:56 -08:00
CocoTheOwner 21a6a19caf Prevent stack-trace spam when placing tile entities where none exist
Improper fix but it suppresses the harmless error.
2021-02-24 18:26:49 +01:00
CocoTheOwner b1d25ec35c Patch NPE 2021-02-23 21:01:19 +01:00
CocoTheOwner 685de56fbb Add proper getval to iris create 2021-02-23 21:00:55 +01:00
CocoTheOwner e8adb7d533 Oops 2021-02-23 20:45:04 +01:00
CocoTheOwner c2af0e8b48 Fix /0 error (It's a poor fix but it works) 2021-02-23 20:13:38 +01:00
CocoTheOwner 8ea7b09482 Replace hint by query
Why would you call this hint???
2021-02-11 11:25:16 +01:00
CocoTheOwner 5180d1a17a Clean up environment getting 2021-02-11 11:21:14 +01:00
CocoTheOwner d7e3493a59 Add get region name 2021-02-11 11:20:57 +01:00
CocoTheOwner 5ea0a53995 Remove required, add getEnv, hasFocus and getFocus 2021-02-11 11:20:47 +01:00
CocoTheOwner dd4c66dbd8 remove unused variables 2021-02-11 11:20:22 +01:00
CocoTheOwner 0e8c2476b2 Add warning for creating worlds without MV or bukkit 2021-02-11 10:17:44 +01:00
CocoTheOwner 53fdcce361 Patch /locate override, reverting cosmetic changes that caused issues. 2021-02-07 20:22:15 +01:00
CocoTheOwner a4a8d78b1f Fix index out of bounds error x2 2021-02-07 18:15:05 +01:00
CocoTheOwner 8b7bb7faa2 Fix index out of bounds error 2021-02-07 18:08:23 +01:00
CocoTheOwner 28430cc1c3 Fix /locate command override 2021-02-07 12:52:07 +01:00
3543 changed files with 610612 additions and 173771 deletions
-49
View File
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
-71
View File
@@ -1,71 +0,0 @@
<factorypath>
<factorypathentry kind="VARJAR" id="M2_REPO/org/spigotmc/spigot-api/1.16.1-R0.1-SNAPSHOT/spigot-api-1.16.1-R0.1-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/commons-lang/commons-lang/2.6/commons-lang-2.6.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/guava/guava/21.0/guava-21.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/md-5/bungeecord-chat/1.16-R0.3/bungeecord-chat-1.16-R0.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/papermc/paperlib/1.0.5/paperlib-1.0.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/bukkit/craftbukkit/cb-1.16.2/1.16.2/cb-1.16.2-1.16.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/sk89q/worldedit/worldedit-bukkit/7.2.0-SNAPSHOT/worldedit-bukkit-7.2.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/sk89q/worldedit/worldedit-core/7.2.0-SNAPSHOT/worldedit-core-7.2.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/sk89q/worldedit/worldedit-libs/core/7.2.0-SNAPSHOT/core-7.2.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/de/schlichtherle/truezip/6.8.3/truezip-6.8.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-profile-default_2.13/0.12.1/truevfs-profile-default_2.13-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/scala-lang/scala-library/2.13.1/scala-library-2.13.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-http/0.12.1/truevfs-driver-http-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-kernel-spec/0.12.1/truevfs-kernel-spec-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-annotations/2.5.0/truecommons-annotations-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/annotations/3.0.0/annotations-3.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-cio/2.5.0/truecommons-cio-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-io/2.5.0/truecommons-io-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-services/2.5.0/truecommons-services-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-logging/2.5.0/truecommons-logging-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/javax/inject/javax.inject/1/javax.inject-1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/httpcomponents/httpcore/4.4.12/httpcore-4.4.12.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/commons-codec/commons-codec/1.11/commons-codec-1.11.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jcl-over-slf4j/1.7.28/jcl-over-slf4j-1.7.28.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-odf/0.12.1/truevfs-driver-odf-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-comp-zipdriver/0.12.1/truevfs-comp-zipdriver-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-comp-zip/0.12.1/truevfs-comp-zip-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/bouncycastle/bcprov-jdk15on/1.63/bcprov-jdk15on-1.63.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-tar/0.12.1/truevfs-driver-tar-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-comp-tardriver/0.12.1/truevfs-comp-tardriver-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-tar-bzip2/0.12.1/truevfs-driver-tar-bzip2-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-tar-gzip/0.12.1/truevfs-driver-tar-gzip-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-tar-xz/0.12.1/truevfs-driver-tar-xz-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/tukaani/xz/1.8/xz-1.8.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-zip-raes/0.12.1/truevfs-driver-zip-raes-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-profile-base_2.13/0.12.1/truevfs-profile-base_2.13-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-access-swing/0.12.1/truevfs-access-swing-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-access/0.12.1/truevfs-access-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-file/0.12.1/truevfs-driver-file-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-jar/0.12.1/truevfs-driver-jar-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-driver-zip/0.12.1/truevfs-driver-zip-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-comp-ibm437/0.12.1/truevfs-comp-ibm437-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truevfs/truevfs-kernel-impl_2.13/0.12.1/truevfs-kernel-impl_2.13-0.12.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-key-console/2.5.0/truecommons-key-console-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-key-default/2.5.0/truecommons-key-default-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-key-swing/2.5.0/truecommons-key-swing-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-key-macosx/2.5.0/truecommons-key-macosx-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-key-spec/2.5.0/truecommons-key-spec-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/truecommons/truecommons-shed/2.5.0/truecommons-shed-2.5.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/java/dev/jna/jna/4.1.0/jna-4.1.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/mozilla/rhino-runtime/1.7.12/rhino-runtime-1.7.12.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/antlr/antlr4-runtime/4.7.2/antlr4-runtime-4.7.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/sk89q/worldedit/worldedit-libs/bukkit/7.2.0-SNAPSHOT/bukkit-7.2.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-slf4j-impl/2.8.1/log4j-slf4j-impl-2.8.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.8.1/log4j-api-2.8.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/bstats/bstats-bukkit/1.7/bstats-bukkit-1.7.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/it/unimi/dsi/fastutil/8.2.1/fastutil-8.2.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/lumine/xikage/MythicMobs/4.9.1/MythicMobs-4.9.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.10/lombok-1.18.10.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/zeroturnaround/zt-zip/1.14/zt-zip-1.14.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar" enabled="true" runInBatchMode="false"/>
</factorypath>
+85
View File
@@ -0,0 +1,85 @@
name: Iris Bug Report
description: File a bug report for Iris
labels: [ bug ]
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill this out!
If this does not work for you, feel free to use the [blank](https://github.com/VolmitSoftware/Iris/issues/new) format.
- type: textarea
id: how
attributes:
label: Problem
description: Please give a text description of how you reached the problem
value: |
1. Install Iris...
2. Do this...
3. Do that...
4. Observe the error...
validations:
required: true
- type: textarea
id: what
attributes:
label: Solution
description: Explain where you think the problem comes from (optional)
placeholder: The code to place a is missing b and c...
validations:
required: false
- type: dropdown
id: platform
attributes:
label: Platform
description: Which server platform is Iris running on? Pick the loader or server software, not the launcher.
options:
- Paper
- Purpur
- Leaf
- Canvas
- Folia
- Spigot / CraftBukkit
- Fabric
- Forge
- NeoForge
- Other (describe under Problem)
validations:
required: true
- type: dropdown
id: mcversion
attributes:
label: Minecraft Version
description: What version of Minecraft is the server on? Iris 4.x targets 26.2 only; older versions are not supported.
options:
- '26.2'
- Other (unsupported)
validations:
required: true
- type: input
id: loaderversion
attributes:
label: Platform / Loader Version
description: Exact server or loader build (see console). For example "Paper 26.2-60", "Fabric Loader 0.19.3", "NeoForge 26.2.0.12-beta".
placeholder: DO NOT SAY "LATEST"
validations:
required: true
- type: input
id: irisversion
attributes:
label: Iris Version
description: What version of Iris are you running? (see console)
placeholder: DO NOT SAY "LATEST"
validations:
required: true
- type: input
id: logs
attributes:
label: Log
description: Paste a full log. Always use [mclogs](https://mclo.gs) Or [Pastebin](https://pastebin.com/). Must not be a crash report. Must be a full log. Must not be a screenshot of a log.
placeholder: https://mslog.gs/...
validations:
required: true
- type: markdown
id: thanks
attributes:
value: "Thank you for filling out the form! We will be with you soon. Please do not ask support to review your report."
+46
View File
@@ -0,0 +1,46 @@
name: Iris Feature Request
description: File a feature request for Iris. If you want to report a bug this is not the place.
labels: [ feature ]
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill this out!
If this does not work for you, feel free to use the [blank](https://github.com/VolmitSoftware/Iris/issues/new) format.
- type: dropdown
id: arc
attributes:
label: Adding, Removing, or Changing
description: What are you doing
options:
- Adding
- Removing
- Changing
validations:
required: true
- type: input
id: atype
attributes:
label: Type of Modification
description: What is it for?
value: I want to ...
validations:
required: true
- type: textarea
id: desc
attributes:
label: What are you trying to modify
description: Give as detailed of a description as you can for the modification that you want done (include pictures if applicable)
value: The way I would implement this is ...
validations:
required: true
- type: textarea
id: alternative
attributes:
label: Alternatives
description: What alternatives have you considered?
value: If this could not be implemented I would ...
- type: markdown
id: thanks
attributes:
value: "Thank you for filling out the form! We will be with you soon. Please do not ask support to review your report."
+73 -11
View File
@@ -1,16 +1,78 @@
/target/
lib/
build/
libs/
dependency-reduced-pom.xml
.gradle/
release/
lint/in.jar
lint/out.jar
lint/mapping.txt
.idea/
.DS_Store
.idea/
collection/
out/
tools/simd-bench/out/
tools/simd-bench/simd-bench.jar
DataPackExamples/
TreeGenStuff/
dist/
core/plugins/
adapters/bukkit/plugin/plugins/
# Dev-run artifacts. `run/` is a loom/ForgeGradle/ModDevGradle working directory and `logs/` is
# server output; neither is ever source. No source directory in this repo is named run or logs.
run/
logs/
*.log
*.log.gz
crash-reports/
hs_err_pid*.log
replay_pid*.log
.codegraph/
plans/
.env
.env.*
!.env.example
local.properties
*.jks
*.p12
*.pfx
*.pem
*.key
*.hprof
credentials.json
service-account*.json
# docs/ is hand-written and tracked (flat numbered product docs). Nothing under it is
# generated, so there is nothing to ignore here.
.qa/
.repro/
.perf/
.release-gate/
# Throwaway worktree copies used by the API / PlaceholderAPI rebuild lanes. Generated, never source.
.apiwt/
.papiwt/
__pycache__/
# Doc authoring scratch
.docs-wip/
AGENTS.md
# Local build tooling, not part of the project
build-lt.sh
BUILDS/
.run/
# Version-content compat lane worktree. Generated, never source.
.compatwt/
.worktrees/
-34
View File
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Iris</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
<filteredResources>
<filter>
<id>1604627025065</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
@@ -1,4 +0,0 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding/<project>=UTF-8
-4
View File
@@ -1,4 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=true
org.eclipse.jdt.apt.genSrcDir=target\\generated-sources\\annotations
org.eclipse.jdt.apt.genTestSrcDir=target\\generated-test-sources\\test-annotations
-9
View File
@@ -1,9 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8
-4
View File
@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
-97
View File
@@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="EclipseModuleManager">
<conelement value="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER" />
<src_description expected_position="2">
<src_folder value="file://$MODULE_DIR$/src/main/java" expected_position="0" />
<src_folder value="file://$MODULE_DIR$/src/main/resources" expected_position="1" />
<src_folder value="file://$MODULE_DIR$/src/test/java" expected_position="4" />
</src_description>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="jdk" jdkName="JavaSE-1.8" jdkType="JavaSDK" />
<orderEntry type="library" name="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER" level="application" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: io.timeandspace:smoothie-map:2.0.2" level="project" />
<orderEntry type="library" name="Maven: io.timeandspace:jpsg-core:1.4" level="project" />
<orderEntry type="library" name="Maven: org.checkerframework:checker-qual:2.11.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-lang:commons-lang:2.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:21.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.md-5:bungeecord-chat:1.16-R0.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.yaml:snakeyaml:1.26" level="project" />
<orderEntry type="library" name="Maven: io.papermc:paperlib:1.0.5" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bukkit.craftbukkit:1.16.3:1.16.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bukkit.craftbukkit:1.16.4:1.16.4" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.bergerkiller.bukkit:BKCommonLib:1.16.4-v2" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit:worldedit-bukkit:7.2.0-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit:worldedit-core:7.2.0-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit.worldedit-libs:core:7.2.0-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: de.schlichtherle:truezip:6.8.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-profile-default_2.13:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.scala-lang:scala-library:2.13.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-http:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-kernel-spec:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-annotations:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:annotations:3.0.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-cio:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-io:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-services:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-logging:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: javax.inject:javax.inject:1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpclient:4.5.10" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.httpcomponents:httpcore:4.4.12" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.11" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.slf4j:jcl-over-slf4j:1.7.28" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-odf:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-comp-zipdriver:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-comp-zip:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bouncycastle:bcprov-jdk15on:1.63" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-tar:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-comp-tardriver:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.commons:commons-compress:1.19" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-tar-bzip2:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-tar-gzip:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-tar-xz:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.tukaani:xz:1.8" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-zip-raes:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-profile-base_2.13:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-access-swing:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-access:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-file:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-jar:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-driver-zip:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-comp-ibm437:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truevfs:truevfs-kernel-impl_2.13:0.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-key-console:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-key-default:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-key-swing:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-key-macosx:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-key-spec:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.truecommons:truecommons-shed:2.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.java.dev.jna:jna:4.1.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.mozilla:rhino-runtime:1.7.12" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.antlr:antlr4-runtime:4.7.2" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit.worldedit-libs:bukkit:7.2.0-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.logging.log4j:log4j-slf4j-impl:2.8.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.logging.log4j:log4j-api:2.8.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bstats:bstats-bukkit:1.7" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: it.unimi.dsi:fastutil:8.2.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: io.lumine.xikage:MythicMobs:4.9.1" level="project" />
<orderEntry type="library" name="Maven: com.github.ben-manes.caffeine:caffeine:2.8.5" level="project" />
<orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.4.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.18.10" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.gson:gson:2.8.5" level="project" />
<orderEntry type="library" name="Maven: org.zeroturnaround:zt-zip:1.14" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.6.6" level="project" />
</component>
</module>
+518
View File
@@ -0,0 +1,518 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the
works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all
versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies also to any other work released this way by its
authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make
sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive
source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and
that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights.
Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it:
responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients
the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must
show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute
and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software.
For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems
will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although
the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the
software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely
where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict
development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger
that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "
Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission,
other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a
work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the Program.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily
liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification), making available to the public, and in some
countries other activities as well.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction
with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright
notice, and (2)
tells the user that there is no warranty for the work (except to the extent that warranties are provided), that
licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a
list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means
any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body,
or, in the case of interfaces specified for a particular programming language, one that is widely used among developers
working in that language.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in
the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to
enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is
available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used
to run it.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (
for an executable work) run the object code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs
which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding
Source includes interface definition files associated with source files for the work, and the source code for shared
libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data
communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the
Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided
the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program.
The output from running a covered work is covered by this License only if the output, given its content, constitutes a
covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license
otherwise remains in force. You may convey covered works to others for the sole purpose of having them make
modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do not control copyright. Those thus making or running
the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that
prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not
allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling
obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the
extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you
disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users,
your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating
that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices
of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for
a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source
code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of
the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or
distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the
compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause
this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library,
need not be included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used
for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In
determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a
particular product received by a particular user, "normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way in which the particular user actually uses, or
expects or is expected to use, the product. A product is a consumer product regardless of whether the product has
substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of
the product.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information
required to install and execute modified versions of a covered work in that User Product from a modified version of its
Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code
is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the
conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to
the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding
Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not
apply if neither you nor any third party retains the ability to install modified object code on the User Product (for
example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support
service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product
in which it has been modified or installed. Access to a network may be denied when the modification itself materially
and adversely affects the operation of the network or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format
that is publicly documented (and with an implementation available to the public in source code form), and must require
no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of
its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were
included in this License, to the extent that they are valid under applicable law. If additional permissions apply only
to part of the Program, that part may be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or
from any part of it. (Additional permissions may be written to require their own removal in certain cases when you
modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have
or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by
the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the
Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with
a term that is a further restriction, you may remove that term. If a license document contains a further restriction but
permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of
that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a
statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as
exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to
propagate or modify it is void, and will automatically terminate your rights under this License (including any patent
licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is
reinstated (a)
provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b)
permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days
after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you
of the violation by some reasonable means, this is the first time you have received notice of violation of this
License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the
notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or
rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not
qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a
covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not
require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered
work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run,
modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third
parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or
subdividing an organization, or merging organizations. If propagation of a covered work results from an entity
transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work
the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with
reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For
example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License,
and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using,
selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the
Program is based. The work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already
acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or
selling its contributor version, but do not include claims that would be infringed only as a consequence of further
modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent
sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential
patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its
contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not
to enforce a patent
(such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a
patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not
available for anyone to copy, free of charge and under the terms of this License, through a publicly available network
server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available,
or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a
manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "
Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in
a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in
that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring
conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is
automatically extended to all recipients of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of,
or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You
may not convey a covered work if you are a party to an arrangement with a third party that is in the business of
distributing software, under which you make payment to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a
discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from
those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered
work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to
infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this
License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to
satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence
you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further
conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License
would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work
licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the
resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special
requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply
to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to
time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the
GNU General Public License "or any later version" applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used,
that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the
Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed
on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING
THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU
ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO
MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to
their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program
in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve
this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to
most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer
to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of
course, your program's commands might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for
the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is
a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If
this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+319 -1
View File
@@ -1,2 +1,320 @@
# Iris
Dimension Generator
Iris is a world generation engine for Minecraft servers and mod loaders. It generates terrain,
biomes, caves, structures, objects, and entities from editable JSON packs, with a full in-game
studio authoring workflow. The same engine runs as a Bukkit-family plugin and as a Fabric, Forge,
or NeoForge server mod. Cross-platform generation targets deterministic parity for identical
artifacts, pack bytes, seeds, and test areas. The master branch targets Minecraft 26.2.
# [Support](https://discord.gg/3xxPTpT) **|** [Documentation](https://github.com/VolmitSoftware/docs/blob/master/iris/00-overview.md) **|** [Git](https://github.com/IrisDimensions)
Authoritative docs live in the central [VolmitSoftware/docs](https://github.com/VolmitSoftware/docs/tree/master/iris) repository, which is the source for the hosted wiki.
Consider supporting development by buying Iris on Spigot.
## Language and localization
Canonical English is defined in the typed Java catalogs under `core/src/main/java/art/arcane/iris/localization`, next to the command, Studio, runtime, and UI surfaces that use it. Iris does not ship an English server translation file; the required Minecraft client asset remains at `assets/irisworldgen/lang/en_us.json`. Complete server bundles and matching client assets are included for German, Spanish, Finnish, French, Hebrew, Italian, Japanese, Korean, Lithuanian, Dutch, Polish, Portuguese, Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese. Set `language` in Iris settings to select one. A JSON file at `languages/overrides/<locale>.json` can override only selected server messages; omitted entries resolve from the bundle and then code-owned English.
## Platforms
| Platform | Artifact | Minecraft | Notes |
|---|---|---|---|
| Paper / Purpur / Leaf / Canvas | plugin jar | 26.1.2 - 26.2 | Full feature set |
| Folia | plugin jar | 26.1.2 - 26.2 | Region-safe scheduling throughout |
| Spigot / CraftBukkit | plugin jar | 26.1.2 - 26.2 | Managed `iris:*` creation and generation; exact vanilla-slot `/iris replace` is unavailable |
| Fabric | mod jar | 26.2 | Server worldgen + client HUD; requires Fabric Loader 0.19.3+ |
| Forge | mod jar | 26.2 | Server worldgen + client HUD; current target is Forge 26.2-65.1.1 |
| NeoForge | mod jar | 26.2 | Server worldgen + client HUD; current target is NeoForge 26.2.0.59 |
Java 25 is required on every platform.
The modded feature set matches the plugin wherever the operation is not Bukkit-bound: worldgen
from Iris packs, authoring with mod blocks/items/entities (with validation suggestions), studio
workspaces with schema autocomplete over the server's live registries, entity spawning parity
including death loot, pregeneration with a boss bar or client HUD, the `/iris` command tree, and
the goldenhash determinism gate, which is interchangeable across all four platforms.
### Native worldgen over Iris terrain
Iris replaces the chunk generator, so vanilla and mod worldgen only runs where Iris runs it. This
is identical on every platform.
| Vanilla / mod worldgen | Over Iris terrain | Control |
|---|---|---|
| Structures (vanilla, datapack, mod) | Yes, on by default | `importedStructures.disabled` denies individual keys |
| Placed features: ores, trees, plants, springs, geodes | Yes, **off by default** | `importedFeatures.enabled` per dimension, with per-step and per-key filters |
| Carvers (caves, canyons, mod carvers) | Never - architectural | Iris has no `NoiseGeneratorSettings` for a carver to sample; use pack `caves`/`carvings` |
| Surface builders and surface rules | Never | Iris builds its surface from pack palettes |
| Mod biomes | Only as a `derivative`, `vanillaDerivative`, `biomeScatter` or `biomeSkyScatter` target | Iris chooses biomes from the pack, not from a biome source |
| Mob spawning, including mod mobs | Yes | Biome spawn tables are merged with the vanilla derivative's |
With `importedFeatures` off - the default - chunk output is byte-for-byte what Iris has always
produced. See [94 - API - Modded](https://github.com/VolmitSoftware/docs/blob/master/iris/94-api-modded.md) and
[01 - Installation & Platforms](https://github.com/VolmitSoftware/docs/blob/master/iris/01-installation-platforms.md) for the full control reference, including
which `pointed_dripstone` keys the 26.2 `speleothem` rename does and does not affect.
Independently of that flag, Iris custom biomes now inherit the biome tags of their vanilla
derivative on every platform, so the emitted datapack tag files change. Anything driven by biome
tags therefore applies to Iris custom biomes: mob variants, spawn rules, and any vanilla or mod
content selecting on `#minecraft:is_overworld` and friends.
## Install
**Plugin (Paper/Purpur/Leaf/Canvas/Folia/Spigot):** drop the plugin jar into `plugins/` and start
the server. First boot performs no pack download. Run `/iris download pack=overworld`,
`/iris download pack=underworld`, or `/iris download link=https://host/path/pack.zip`, waiting for
each download to finish before starting another. The current built-in packs declare no external
datapack imports. Restart after download so Minecraft loads their dimension types and biomes.
Custom packs that declare `datapackImports` must complete their external-datapack installation and
registry restart first. Bukkit accepts Modrinth, direct HTTP(S), and absolute local `file:` ZIP URLs;
ZIPs under `plugins/Iris/datapacks/imports/` are discovered for every Iris dimension. Plain Spigot
supports ordinary managed `/iris create`, but not
the early-bootstrap `/iris replace` path for canonical Overworld, Nether, or End slots.
**Mod (Fabric/Forge/NeoForge):** drop the mod jar into `mods/` and start the server. The jar is
self-contained (core, SPI, and required Fabric API modules are bundled). First boot compiles only
packs already on disk and never accesses the network. `/iris download` installs a pack atomically
without stopping the server. The current built-in packs declare no external datapack imports.
Custom packs that declare them require compatible archives in that save's `datapacks/` directory;
modded `/iris datapack ingest` is an explanatory stub and does not install them. Restart only after
the Iris pack and all of its inputs are present. Packs register their custom dimension types,
height ranges, biomes, and external structure keys during that boot; worlds created
before it run with fallback registry data.
**Singleplayer (modded clients):** installed Iris packs appear as selectable World Types on the
Create New World screen; the integrated server runs the same engine.
## The client mod
Installing the mod jar on a client adds a native pregeneration HUD: a top-left panel with a
progress bar, chunks done/total, percent, chunks per second, and ETA, turning yellow while the
pregen is paused. The `H` key (rebindable, under the "Iris" controls category) toggles it.
The HUD works against modded Iris servers and against Bukkit/Paper Iris servers, both over the
`irisworldgen:main` channel (custom payloads on modded, plugin messaging on Bukkit). Vanilla
clients are unaffected and get the server-side boss bar instead; on non-Iris servers the client
mod is inert.
## Quickstart
Complete the platform's pack, external-datapack, and registry-restart workflow above first. Then
create and enter an Iris world.
Plugin (optional arguments are keyed):
```
/iris create myworld type=overworld seed=1337
/iris tp myworld
```
Mod (positional arguments):
```
/iris create myworld overworld 1337
/iris tp irisworldgen:myworld
```
On Paper-family servers with early bootstrap (not plain Spigot), the shipping pair can instead
replace the canonical portal-linked slots after their registry workflow is complete:
```
/iris replace minecraft:overworld type=overworld seed=123456789
/iris replace minecraft:the_nether type=underworld seed=-987654321
```
Restart once after both commands report staged. The independent seeds apply to their respective
slots, and vanilla portals keep routing between the canonical Overworld and Nether identities.
Pregeneration requires a radius in blocks. On the plugin, optional arguments are keyed; on modded
servers they are positional and composable:
```
/iris pregen start 352 world=myworld center=0,0 gui=false
/iris pregen start 352 irisworldgen:myworld at 0 0 sync
```
Players with the client mod see the native HUD; everyone else gets a boss bar (modded) or
console/status output. `/iris pregen status` reports progress on the plugin.
## Studio and VSCode workspace
The studio is the pack authoring environment, available on all platforms. Studio worlds are
transient - they are deleted on close and purged at startup.
Plugin:
```
/iris studio create name=<name> [template=<pack>]
/iris studio open <pack> [seed=1337]
/iris studio vscode [dimension=<pack>]
/iris studio update [dimension=<pack>]
/iris studio close
```
Mod:
```
/iris studio create [name] [template]
/iris studio open <pack> [seed]
/iris studio vscode [pack]
/iris studio update [pack]
/iris studio close
```
The generated VSCode workspace wires per-type JSON schemas (dimensions, biomes, regions, objects,
loot, entities, snippets) for full autocomplete. Schemas are generated from the server's live
registries, so on modded servers block, item, entity, enchantment, and potion-effect completion
includes installed mod content (for example `create:brass_ingot`). Editing an open studio's pack
files hotloads the changes and regenerates the schemas.
## PlaceholderAPI
Iris registers the `iris` expansion when PlaceholderAPI is enabled. Paths are dot-separated,
lowercase, and never contain an underscore. Every value is plain text: no colour codes, no unit
suffixes, no `%` character, `.` as the decimal separator, and no thousands grouping.
Three answers are possible. A path that is not in the list below returns nothing, so PlaceholderAPI
re-emits the literal `%iris_...%` and a typo stays visible. A known path with no value right now
returns `---`. A real zero returns `0`.
| Placeholder | Value |
|---|---|
| `%iris_available%` | `true` when the Iris terrain service is live |
| `%iris_world.available%` | `true` when the reading player is in an Iris world and a reading exists |
| `%iris_world.biome%` | Surface biome display name at the player, e.g. `Hot Desert Dunes` |
| `%iris_world.biome-key%` | Surface biome load key, e.g. `desert/hot-dunes` |
| `%iris_world.biome-custom%` | `true` when the surface biome defines custom derivatives, otherwise `false` |
| `%iris_world.biome-custom-id%` | Authored `customDerivitives[].id` when exactly one derivative is defined, for example `golden-dunes`; `---` for none or multiple |
| `%iris_world.biome-custom-ids%` | All authored custom derivative IDs, comma-and-space separated in definition order; `---` for none |
| `%iris_world.biome-custom-key%` | Namespaced Minecraft registry key for the sole custom derivative, such as `iris:biomes/<hash>`; `---` for none, multiple, or unavailable mapping |
| `%iris_world.biome-custom-keys%` | Registry keys for all custom derivatives in definition order, comma-and-space separated; `---` for none or any unavailable mapping |
| `%iris_world.biome-custom-count%` | Number of authored custom derivatives; `0` for a vanilla-only biome |
| `%iris_world.biome-derivative%` | Configured base derivative registry key, for example `minecraft:desert` |
| `%iris_world.biome-vanilla-derivative%` | Effective vanilla derivative registry key used when a vanilla biome is required |
| `%iris_world.biome-type%` | Inferred biome category: `land`, `sea`, `shore`, or `cave`; `---` when unavailable |
| `%iris_world.min-height%` | World minimum absolute Y, inclusive |
| `%iris_world.max-height%` | World maximum absolute Y, exclusive |
| `%iris_world.height%` | World vertical span, equal to maximum minus minimum height |
| `%iris_world.fluid-height%` | Dimension fluid level in absolute world Y; this is configuration, not a measurement of water at the player |
| `%iris_world.region%` | Region display name at the player |
| `%iris_world.region-key%` | Region load key |
| `%iris_world.dimension%` | Dimension (pack) load key of the player's world |
| `%iris_pregen.available%` | `true` while a pregeneration job is running |
| `%iris_pregen.world%` | World name the running job is pregenerating |
| `%iris_pregen.percent%` | Completion, `0.00` to `100.00`, no `%` character |
| `%iris_pregen.eta%` | Estimated seconds remaining, whole number |
| `%iris_pregen.eta-text%` | Same estimate as `2m 5s` or `1h 30m` |
| `%iris_pregen.chunks%` | Chunks generated so far |
| `%iris_pregen.total%` | Chunks in the job |
| `%iris_pregen.chunks-per-second%` | Current rate |
| `%iris_pregen.paused%` | `true` while the job is paused |
Custom derivative IDs are authored metadata. A singular ID or registry key is available only when the biome defines one derivative; plural keys list all definitions. Saved columns retain their historical biome definitions.
Biome and region values describe the surface at the player's block column. Height values describe world configuration. Walking refreshes them at most
once per second per player, so a whole board of `world.*` keys costs one refresh per player per
second no matter how many of them are on it, and a value may lag a sprinting player by up to a
second. A jump that is not walking - joining, respawning, changing worlds, stepping through a portal,
or any teleport including `/iris goto`, `/tp`, an ender pearl and a random teleport - is published
immediately, so a player who arrives somewhere and then stands still never keeps reading the biome,
region or dimension of where they came from. `pregen.*` is global: there is one pregeneration job per
server, and `%iris_pregen.world%` says which world it is.
### Migration from the pre-2.0 keys
The old underscore keys are gone. There is no alias and no dual-accept window; an old key now
renders literally so it is visible rather than silently wrong.
| Old key | New key | Why |
|---|---|---|
| `%iris_biome_name%` | `%iris_world.biome%` | Renamed onto the dot grammar |
| `%iris_biome_id%` | `%iris_world.biome-key%` | Renamed; `id` was always the load key |
| `%iris_region_name%` | `%iris_world.region%` | Renamed onto the dot grammar |
| `%iris_region_id%` | `%iris_world.region-key%` | Renamed; `id` was always the load key |
| `%iris_biome_file%` | removed | Rendered an absolute server path into player-visible text, and threw on packs with no backing file |
| `%iris_region_file%` | removed | Same as `biome_file` |
| `%iris_world_seed%` | removed | Handed the world seed to anyone who could read a scoreboard, and a placeholder has no permission context to gate on |
| `%iris_terrain_height%` | removed | Reported the *generated* height, before objects and player edits, so it disagreed with the block under the player's feet |
| `%iris_terrain_slope%` | removed | Three extra noise samples per read for an unformatted pack-authoring diagnostic |
| `%iris_world_mode%` | removed | Studio or Production; a studio world exists for seconds during authoring and is never on a live board |
| `%iris_world_speed%` | removed | Mutated engine rate-window state every time it was read. `%iris_pregen.chunks-per-second%` answers the same question from a snapshot |
The old keys also read the *cave* biome for a player standing under an overhang, because they
sampled two blocks above the player's feet. The new `world.biome` is always the surface biome,
which is what a board reader means.
## Building from source
Requirements: JDK 25 (set `JAVA_HOME` to it). The Gradle wrapper handles everything else.
```
./gradlew buildAllToOut
```
builds every platform artifact into the workspace-level `../PluginOuts/` directory:
```
Iris v<version> [CraftBukkit] <mc>.jar
Iris v<version> [Fabric] <mc>+<loader>.jar
Iris v<version> [Forge] <mc>+<loader>.jar
Iris v<version> [NeoForge] <mc>+<loader>.jar
```
Per-platform tasks: `./gradlew buildBukkit`, `buildFabric`, `buildForge`, `buildNeoforge`. The
SPI jar (the pure-JVM adapter/platform contract, not the stable plugin API) is built to `spi/build/libs/` by
`./gradlew :spi:jar`.
`./gradlew buildAll` also runs `buildAllToOut` and copies the jars into a consumer dropin tree
for a local test server. The consumer directory defaults to `../../[Minecraft Server]/consumers/`
when it exists, otherwise `build/consumers/` inside the repo. `-Plocation=/path/to/consumers`
changes only the consumer directory. Both tasks copy all four verified platform jars into
`../PluginOuts/`.
If you need help compiling as a developer or contributor, ask in the Discord.
## Adapters / modded development
`core/` and `spi/` are pure JVM. `adapters/bukkit/` is part of the root Gradle build; the three
modded adapters (`adapters/fabric`, `adapters/forge`, `adapters/neoforge`) are standalone builds
with their own `settings.gradle`, which is what keeps Loom, ForgeGradle, and ModDevGradle off one
plugin classpath. Drive them with `-p`:
```
./gradlew -p adapters/fabric runServer # or runClient
./gradlew -p adapters/forge runServer
./gradlew -p adapters/neoforge runServer
./gradlew -p adapters/fabric test # shared adapters/modded-common test suite
```
Each `runServer` accepts determinism and world-integrity flags, forwarded to the game as system
properties:
| Flag | System property | Purpose |
|---|---|---|
| `-PirisParity=<pack>` | `iris.parity` | Run the cross-platform parity harness for a pack |
| `-PirisParityGolden=<file>` | `iris.parity.golden` | Compare against a captured golden-hash file |
| `-PirisParityDeep=true` | `iris.parity.deep` | Deep (per-block) parity instead of hash-only |
| `-PirisWorldCheck=<world>` | `iris.worldcheck` | Post-generation world integrity check |
Fabric additionally takes `-PirisClientRunDir=<dir>` to relocate the `runClient` working directory.
Shared code lives in `adapters/minecraft-common` (all adapters), `adapters/modded-common`
(loaders + the shared test suite), and `adapters/client-common` (client HUD and world-type
screens); every adapter adds those source directories, so one edit reaches all three loaders.
For IDE import you can surface the three adapter builds in the root composite with
`-PincludeModdedAdapters=true`. It is off by default: each adapter includes the root build back to
substitute `art.arcane:core` and `art.arcane:spi`, so including them from the root closes a
composite cycle. The build and release paths do not need it.
## Documentation
Full product docs live in the central [VolmitSoftware/docs](https://github.com/VolmitSoftware/docs/tree/master/iris) repository. Start with:
- [Overview and index](https://github.com/VolmitSoftware/docs/blob/master/iris/00-overview.md)
- [Installation and platforms](https://github.com/VolmitSoftware/docs/blob/master/iris/01-installation-platforms.md)
- [Getting started](https://github.com/VolmitSoftware/docs/blob/master/iris/02-getting-started.md)
- [Structures overview](https://github.com/VolmitSoftware/docs/blob/master/iris/18-structures-overview.md)
- [API — Getting Started](https://github.com/VolmitSoftware/docs/blob/master/iris/90-api-getting-started.md)
- [Maintainer — MC version bump](https://github.com/VolmitSoftware/docs/blob/master/iris/85-maintainer-mc-version-bump.md)
- [Maintainer — release checklist](https://github.com/VolmitSoftware/docs/blob/master/iris/86-maintainer-release-checklist.md)
@@ -0,0 +1,509 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.pack.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.structure.nativegen.NativeStructureFrequencyScale;
import art.arcane.iris.structure.nativegen.IrisImportedStructureControl;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
final class DatapackStructureStateFilter {
private DatapackStructureStateFilter() {
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
return filter(structureSets, scopeIndex, declaredSources, new IrisImportedStructureControl());
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures
) {
return filter(
structureSets,
scopeIndex,
declaredSources,
importedStructures,
keyIndex(structureSets));
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (scopeIndex.isEmpty()) {
return new Selection(
scaleFrequencyOverrides(structureSets, importedStructures, keyIndex),
0,
0);
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity = new IdentityHashMap<>();
Set<Holder<StructureSet>> visiting = Collections.newSetFromMap(new IdentityHashMap<>());
List<Holder<StructureSet>> filteredSets = new ArrayList<>(structureSets.size());
int retainedManagedSets = 0;
int excludedManagedSets = 0;
for (Holder<StructureSet> holder : structureSets) {
String setKey = structureSetKey(holder, keyIndex);
boolean managedSet = setKey != null && scopeIndex.isManagedStructureSet(setKey);
Holder<StructureSet> scopedHolder = scopeHolder(
holder,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (scopedHolder == null) {
if (managedSet) {
excludedManagedSets++;
}
continue;
}
if (managedSet) {
retainedManagedSets++;
}
filteredSets.add(scopedHolder);
}
return new Selection(
scaleFrequencyOverrides(
List.copyOf(filteredSets), importedStructures, keyIndex),
retainedManagedSets,
excludedManagedSets);
}
static String structureSetKey(Holder<StructureSet> holder) {
return structureSetKey(holder, keyIndex(List.of(holder)));
}
static StructureSetKeyIndex keyIndex(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
return StructureSetKeyIndex.create(registeredStructureSets);
}
private static String structureSetKey(
Holder<StructureSet> holder,
StructureSetKeyIndex keyIndex
) {
return keyIndex.resolve(holder)
.map(key -> key.identifier().toString())
.orElse(null);
}
private static List<StructureSet.StructureSelectionEntry> filterEntries(
List<StructureSet.StructureSelectionEntry> entries,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
List<StructureSet.StructureSelectionEntry> filtered = new ArrayList<>(entries.size());
for (StructureSet.StructureSelectionEntry entry : entries) {
String structureKey = entry.structure().unwrapKey()
.map(key -> key.identifier().toString())
.orElse(null);
if (structureKey == null || scopeIndex.allowsStructure(structureKey, declaredSources)) {
filtered.add(entry);
}
}
return filtered;
}
private static Holder<StructureSet> scopeHolder(
Holder<StructureSet> holder,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
if (scopedByIdentity.containsKey(holder)) {
return scopedByIdentity.get(holder);
}
if (!visiting.add(holder)) {
return null;
}
try {
String setKey = structureSetKey(holder, keyIndex);
if (setKey != null && scopeIndex.isManagedStructureSet(setKey)
&& !scopeIndex.allowsStructureSet(setKey, declaredSources)) {
scopedByIdentity.put(holder, null);
return null;
}
StructureSet originalSet = holder.value();
List<StructureSet.StructureSelectionEntry> entries = filterEntries(
originalSet.structures(), scopeIndex, declaredSources);
if (entries.isEmpty()) {
scopedByIdentity.put(holder, null);
return null;
}
StructurePlacement placement = scopePlacement(
originalSet.placement(),
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (entries.size() == originalSet.structures().size()
&& placement == originalSet.placement()) {
scopedByIdentity.put(holder, holder);
return holder;
}
Holder<StructureSet> scoped = replacementHolder(
holder, new StructureSet(entries, placement), keyIndex);
scopedByIdentity.put(holder, scoped);
return scoped;
} finally {
visiting.remove(holder);
}
}
static Optional<StructurePlacement.ExclusionZone> exclusionZone(StructurePlacement placement) {
Object value = declaredFieldValue(StructurePlacement.class, placement, Optional.class);
if (value instanceof Optional<?> optional && (optional.isEmpty()
|| optional.get() instanceof StructurePlacement.ExclusionZone)) {
@SuppressWarnings("unchecked")
Optional<StructurePlacement.ExclusionZone> resolved =
(Optional<StructurePlacement.ExclusionZone>) optional;
return resolved;
}
throw new IllegalStateException("Could not read structure placement exclusion zone from "
+ placement.getClass().getName());
}
private static StructurePlacement scopePlacement(
StructurePlacement placement,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
Optional<StructurePlacement.ExclusionZone> currentZone = exclusionZone(placement);
if (currentZone.isEmpty()) {
return placement;
}
Holder<StructureSet> target = currentZone.get().otherSet();
String targetKey = structureSetKey(target, keyIndex);
if (targetKey != null) {
target = holdersByKey.getOrDefault(targetKey, target);
}
Holder<StructureSet> scopedTarget = scopeHolder(
target,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
Optional<StructurePlacement.ExclusionZone> scopedZone = Optional.empty();
if (scopedTarget != null) {
scopedZone = Optional.of(new StructurePlacement.ExclusionZone(
scopedTarget,
currentZone.get().chunkCount()));
}
if (scopedTarget == currentZone.get().otherSet()) {
return placement;
}
return copyPlacement(placement, scopedZone, 1D);
}
private static List<Holder<StructureSet>> scaleFrequencyOverrides(
List<Holder<StructureSet>> structureSets,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (!importedStructures.hasFrequencyOverrides()) {
return structureSets;
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity = new IdentityHashMap<>();
List<Holder<StructureSet>> scaledSets = new ArrayList<>(structureSets.size());
boolean changed = false;
for (Holder<StructureSet> holder : structureSets) {
Holder<StructureSet> scaled = scaleFrequencyHolder(
holder,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
scaledSets.add(scaled);
changed |= scaled != holder;
}
return changed ? List.copyOf(scaledSets) : structureSets;
}
private static Holder<StructureSet> scaleFrequencyHolder(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity,
StructureSetKeyIndex keyIndex
) {
if (scaledByIdentity.containsKey(holder)) {
return scaledByIdentity.get(holder);
}
if (!dependsOnFrequencyOverride(
holder, importedStructures, holdersByKey, keyIndex)) {
scaledByIdentity.put(holder, holder);
return holder;
}
ResourceKey<StructureSet> holderKey = keyIndex.resolve(holder).orElseThrow(() ->
new IllegalStateException(
"An affected native structure-set exclusion graph has an unkeyed holder"));
ReboundStructureSetHolder scaledHolder = new ReboundStructureSetHolder(holderKey);
scaledByIdentity.put(holder, scaledHolder);
StructureSet originalSet = holder.value();
StructurePlacement originalPlacement = originalSet.placement();
Optional<StructurePlacement.ExclusionZone> originalZone = exclusionZone(originalPlacement);
Optional<StructurePlacement.ExclusionZone> scaledZone = originalZone;
if (originalZone.isPresent()) {
Holder<StructureSet> target = canonicalHolder(
originalZone.get().otherSet(), holdersByKey, keyIndex);
Holder<StructureSet> scaledTarget = scaleFrequencyHolder(
target,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
if (scaledTarget != originalZone.get().otherSet()) {
scaledZone = Optional.of(new StructurePlacement.ExclusionZone(
scaledTarget, originalZone.get().chunkCount()));
}
}
double multiplier = importedStructures.frequencyMultiplier(
structureSetKey(holder, keyIndex));
StructurePlacement scaledPlacement = multiplier == 1D && scaledZone.equals(originalZone)
? originalPlacement
: copyPlacement(originalPlacement, scaledZone, multiplier);
scaledHolder.bind(new StructureSet(originalSet.structures(), scaledPlacement));
return scaledHolder;
}
private static boolean dependsOnFrequencyOverride(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
Set<Holder<StructureSet>> visited = Collections.newSetFromMap(new IdentityHashMap<>());
Holder<StructureSet> current = holder;
while (visited.add(current)) {
if (importedStructures.frequencyMultiplier(
structureSetKey(current, keyIndex)) != 1D) {
return true;
}
Optional<StructurePlacement.ExclusionZone> zone =
exclusionZone(current.value().placement());
if (zone.isEmpty()) {
return false;
}
current = canonicalHolder(zone.get().otherSet(), holdersByKey, keyIndex);
}
return false;
}
private static Holder<StructureSet> canonicalHolder(
Holder<StructureSet> holder,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
String key = structureSetKey(holder, keyIndex);
return key == null ? holder : holdersByKey.getOrDefault(key, holder);
}
private static Holder<StructureSet> replacementHolder(
Holder<StructureSet> original,
StructureSet replacement,
StructureSetKeyIndex keyIndex
) {
Optional<ResourceKey<StructureSet>> key = keyIndex.resolve(original);
if (key.isEmpty()) {
return Holder.direct(replacement);
}
ReboundStructureSetHolder holder = new ReboundStructureSetHolder(key.get());
holder.bind(replacement);
return holder;
}
private static StructurePlacement copyPlacement(
StructurePlacement placement,
Optional<StructurePlacement.ExclusionZone> exclusionZone,
double frequencyMultiplier
) {
Vec3i locateOffset = (Vec3i) declaredFieldValue(
StructurePlacement.class, placement, Vec3i.class);
StructurePlacement.FrequencyReductionMethod frequencyReductionMethod =
(StructurePlacement.FrequencyReductionMethod) declaredFieldValue(
StructurePlacement.class,
placement,
StructurePlacement.FrequencyReductionMethod.class);
float frequency = (float) declaredFieldValue(
StructurePlacement.class, placement, float.class);
int salt = (int) declaredFieldValue(
StructurePlacement.class, placement, int.class);
if (placement instanceof RandomSpreadStructurePlacement randomSpread) {
NativeStructureFrequencyScale scale = NativeStructureFrequencyScale.randomSpread(
frequency, randomSpread.spacing(), randomSpread.separation(), frequencyMultiplier);
return new RandomSpreadStructurePlacement(
locateOffset,
frequencyReductionMethod,
scale.frequency(),
salt,
exclusionZone,
scale.spacing(),
randomSpread.separation(),
randomSpread.spreadType());
}
if (placement.getClass() == ConcentricRingsStructurePlacement.class) {
ConcentricRingsStructurePlacement rings =
(ConcentricRingsStructurePlacement) placement;
float scaledFrequency = NativeStructureFrequencyScale.probability(
frequency, frequencyMultiplier);
return new ConcentricRingsStructurePlacement(
locateOffset,
frequencyReductionMethod,
scaledFrequency,
salt,
exclusionZone,
rings.distance(),
rings.spread(),
rings.count(),
rings.preferredBiomes());
}
throw new IllegalStateException("Unsupported native structure placement in an affected "
+ "frequency-override or datapack-scope graph: "
+ placement.getClass().getName());
}
private static Object declaredFieldValue(
Class<?> declaringType,
Object target,
Class<?> fieldType
) {
Field match = null;
for (Field field : declaringType.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != fieldType) {
continue;
}
if (match != null) {
throw new IllegalStateException("Ambiguous " + fieldType.getName() + " field on "
+ declaringType.getName());
}
match = field;
}
if (match == null) {
throw new IllegalStateException("Missing " + fieldType.getName() + " field on "
+ declaringType.getName());
}
try {
match.setAccessible(true);
return match.get(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not read " + fieldType.getName() + " field on "
+ declaringType.getName(), e);
}
}
static final class StructureSetKeyIndex {
private final Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries;
private StructureSetKeyIndex(
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries
) {
this.keysByEntries = keysByEntries;
}
private static StructureSetKeyIndex create(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>> keys =
new IdentityHashMap<>();
for (Holder<StructureSet> holder : registeredStructureSets) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isEmpty()) {
continue;
}
List<StructureSet.StructureSelectionEntry> entries = holder.value().structures();
ResourceKey<StructureSet> previous = keys.put(entries, key.get());
if (previous != null && !previous.equals(key.get())) {
throw new IllegalStateException(
"Multiple registered structure sets share the same entry list");
}
}
return new StructureSetKeyIndex(keys);
}
private Optional<ResourceKey<StructureSet>> resolve(Holder<StructureSet> holder) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isPresent()) {
return key;
}
return Optional.ofNullable(keysByEntries.get(holder.value().structures()));
}
}
record Selection(
List<Holder<StructureSet>> structureSets,
int retainedManagedSets,
int excludedManagedSets
) {
}
private static final class ReboundStructureSetHolder extends Holder.Reference<StructureSet> {
private ReboundStructureSetHolder(ResourceKey<StructureSet> key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, key, null);
}
private void bind(StructureSet structureSet) {
bindValue(structureSet);
}
}
}
@@ -0,0 +1,440 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.decoration.IrisDecorationStep;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
import art.arcane.iris.structure.object.IrisStaticObjectLayer;
import art.arcane.iris.spi.IrisLogging;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntBinaryOperator;
/**
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
* {@code importedFeatures.enabled} the vanilla placed-feature pass runs over Iris terrain, and with the
* control off nothing here allocates or runs.
*
* <p>Iris does its own native structure pass and calls the delegate with {@code addVanillaDecorations=false},
* so the vanilla feature half never runs on its own. This reproduces that half only - structures are never
* placed twice.
*
* <p>Threading: called from {@code applyBiomeDecoration} on the worldgen thread that owns the chunk. The
* FEATURES chunk step is not parallel-safe.
*/
final class ImportedFeatureStage {
private static final String CYCLE_MARKER = "Feature order cycle found";
private final Engine engine;
private final ConcurrentHashMap<Integer, RuntimeFeatureState> runtimeStates = new ConcurrentHashMap<>();
ImportedFeatureStage(Engine engine) {
this.engine = engine;
}
/**
* Generation settings for one biome holder, mapping Iris custom biomes onto their vanilla derivative.
* Pass-through while the control is off, which is what keeps {@code BiomeFilter} behaving exactly as it
* does today.
*/
BiomeGenerationSettings generationSettings(Holder<Biome> biome) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return null;
}
Holder<Biome> mapped = table.derivatives().get(holderKey(biome));
return mapped == null ? null : mapped.value().getGenerationSettings();
}
/**
* Builds the table on the first decorated chunk, or leaves the stage inert. A feature-order cycle is
* reported once here and degrades to features-off; it never reaches chunk generation as a crash.
*/
void prepare(WorldGenLevel level) {
IrisDimension dimension = engine.getDimension();
int runtimeId = engine.getCacheID();
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
synchronized (this) {
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
runtimeStates.put(runtimeId, build(level, dimension));
}
}
void evictRuntime(int runtimeId) {
runtimeStates.remove(runtimeId);
}
private RuntimeFeatureState currentState() {
IrisDimension dimension = engine.getDimension();
RuntimeFeatureState state = runtimeStates.get(engine.getCacheID());
return settled(state, dimension) ? state : null;
}
private static boolean settled(RuntimeFeatureState state, IrisDimension dimension) {
return state != null && state.dimension() == dimension;
}
private RuntimeFeatureState build(WorldGenLevel level, IrisDimension dimension) {
IrisImportedFeatureControl control;
try {
control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) {
IrisLogging.error("Iris could not read importedFeatures for this dimension; features off: "
+ error);
return new RuntimeFeatureState(dimension, null);
}
if (!control.shouldGenerateFeatures()) {
return new RuntimeFeatureState(dimension, null);
}
FeatureTable built;
try {
built = buildTable(level, control, dimension);
} catch (Throwable error) {
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": feature table construction failed: " + error);
return new RuntimeFeatureState(dimension, null);
}
if (built == null) {
return new RuntimeFeatureState(dimension, null);
}
IrisLogging.info("Iris importedFeatures on for " + dimensionKey() + ": " + built.biomes().size()
+ " biomes, " + built.steps().size() + " steps, " + built.derivatives().size()
+ " custom-biome derivative maps");
return new RuntimeFeatureState(dimension, built);
}
private FeatureTable buildTable(WorldGenLevel level, IrisImportedFeatureControl control,
IrisDimension dimension) {
Registry<Biome> registry = level.registryAccess().lookupOrThrow(Registries.BIOME);
Set<String> visibleKeys = visibleBiomeKeys();
// Registry-ordered walk, never a hash-ordered set: FeatureSorter's cycle detection walks this list and
// an unordered walk makes detection depend on JVM hash order.
List<Holder<Biome>> biomes = new ArrayList<>();
Map<String, Holder<Biome>> byKey = new HashMap<>();
registry.listElements().forEach((Holder.Reference<Biome> reference) -> {
String key = holderKey(reference);
if (key != null && visibleKeys.contains(key)) {
biomes.add(reference);
byKey.put(key, reference);
}
});
if (biomes.isEmpty()) {
IrisLogging.error("Iris importedFeatures is on but " + dimensionKey()
+ " exposes no registered biomes; features off");
return null;
}
Map<String, Holder<Biome>> derivatives = customBiomeDerivatives(registry, byKey);
List<FeatureSorter.StepFeatureData> steps;
try {
steps = FeatureSorter.buildFeaturesPerStep(biomes,
(Holder<Biome> biome) -> settingsFor(biome, derivatives).features(), true);
} catch (IllegalStateException error) {
String message = error.getMessage();
if (message == null || !message.contains(CYCLE_MARKER)) {
throw error;
}
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": the registered placed features cannot be ordered. " + message
+ ". Remove or reorder the conflicting content, or leave importedFeatures.enabled false.");
return null;
}
boolean filtered = control.getDisabled() != null && !control.getDisabled().isEmpty();
return new FeatureTable(dimension, control, List.copyOf(biomes), Set.copyOf(biomes),
Map.copyOf(byKey), steps, Map.copyOf(derivatives), filtered);
}
/**
* Every biome key Iris can write into a chunk section: the structure derivative, the raw derivative, every
* scatter entry, and every generated custom biome.
*/
private Set<String> visibleBiomeKeys() {
Set<String> keys = new LinkedHashSet<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
addKey(keys, irisBiome.getStructureDerivativeKey());
addKey(keys, irisBiome.getDerivativeKey());
addKey(keys, irisBiome.getVanillaDerivativeKey());
for (String scatter : irisBiome.getBiomeScatter()) {
addKey(keys, scatter);
}
for (String scatter : irisBiome.getBiomeSkyScatter()) {
addKey(keys, scatter);
}
if (!irisBiome.isCustom()) {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
addKey(keys, engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome));
}
}
return keys;
}
/**
* Maps every generated Iris custom biome key onto the registry holder of its Iris biome's vanilla
* derivative. The custom biome's own datapack JSON carries no features by design.
*/
private Map<String, Holder<Biome>> customBiomeDerivatives(Registry<Biome> registry,
Map<String, Holder<Biome>> byKey) {
Map<String, Holder<Biome>> derivatives = new HashMap<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
if (!irisBiome.isCustom()) {
continue;
}
String derivativeKey = normalizeKey(irisBiome.getVanillaDerivativeKey());
// Resolve from the registry, not only from the visible biome set: a sea or shore biome's structure
// derivative is rewritten away from its vanilla derivative, so the derivative whose features we
// want is not always a biome Iris can emit.
Holder<Biome> derivative = byKey.get(derivativeKey);
if (derivative == null) {
derivative = resolveHolder(registry, derivativeKey);
}
if (derivative == null) {
IrisLogging.warn("Iris importedFeatures: vanilla derivative " + derivativeKey + " of biome "
+ irisBiome.getLoadKey()
+ " is not registered; its custom biomes generate no imported features");
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
derivatives.put(
engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome),
derivative
);
}
}
return derivatives;
}
private static Holder<Biome> resolveHolder(Registry<Biome> registry, String key) {
if (registry == null || key == null || key.isBlank()) {
return null;
}
Identifier identifier = Identifier.tryParse(key);
if (identifier == null) {
return null;
}
return registry.get(identifier).<Holder<Biome>>map((Holder.Reference<Biome> reference) -> reference)
.orElse(null);
}
private static BiomeGenerationSettings settingsFor(Holder<Biome> biome,
Map<String, Holder<Biome>> derivatives) {
Holder<Biome> mapped = derivatives.get(holderKey(biome));
return mapped == null
? biome.value().getGenerationSettings()
: mapped.value().getGenerationSettings();
}
/**
* Runs the vanilla placed-feature pass for one chunk on the calling worldgen thread. A no-op while the
* control is disabled or the table degraded.
*/
void run(WorldGenLevel level, ChunkAccess chunk, ChunkGenerator owner) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return;
}
ChunkPos centerPos = chunk.getPos();
SectionPos sectionPos = SectionPos.of(centerPos, level.getMinSectionY());
BlockPos origin = sectionPos.origin();
Registry<PlacedFeature> featureRegistry = level.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
List<FeatureSorter.StepFeatureData> steps = table.steps();
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
DimensionStackContext stackContext = engine.getDimensionStackContext();
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table, stackContext);
IntBinaryOperator surfaceFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, false) + staticMinY + 1;
IntBinaryOperator floorFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, true) + staticMinY + 1;
WorldGenLevel placementLevel = staticObjects.isEmpty() && stackContext == null
? level
: NativeStructureWorldgenAccess.create(
level, centerPos,
surfaceFirstFreeY,
floorFirstFreeY,
stackContext != null,
NativeGenerationWriteGuard.protectedPositions(staticObjects, stackContext, staticMinY));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
continue;
}
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
random, decorationSeed, origin, stepIndex);
}
} catch (Throwable error) {
throw new IllegalStateException("Iris imported feature placement failed for chunk "
+ centerPos.x() + "," + centerPos.z(), error);
} finally {
level.setCurrentlyGenerating(null);
}
}
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
BlockPos origin, int stepIndex) {
IntSet stepFeatures = new IntArraySet();
for (Holder<Biome> biome : chunkBiomes) {
List<HolderSet<PlacedFeature>> biomeFeatures = settingsFor(biome, table.derivatives()).features();
if (stepIndex >= biomeFeatures.size()) {
continue;
}
for (Holder<PlacedFeature> feature : biomeFeatures.get(stepIndex)) {
stepFeatures.add(stepData.indexMapping().applyAsInt(feature.value()));
}
}
if (stepFeatures.isEmpty()) {
return;
}
// Sorted global indices: identical ordering to vanilla, and each feature's seed comes from its own
// global index, so denying one feature never shifts another.
int[] featureIndices = stepFeatures.toIntArray();
Arrays.sort(featureIndices);
for (int globalIndex : featureIndices) {
PlacedFeature feature = stepData.features().get(globalIndex);
if (table.filtered()) {
Identifier featureId = featureRegistry.getKey(feature);
if (featureId != null && !table.control().shouldGenerate(featureId.toString())) {
continue;
}
}
random.setFeatureSeed(decorationSeed, globalIndex, stepIndex);
level.setCurrentlyGenerating(() -> describeFeature(featureRegistry, feature));
feature.placeWithBiomeCheck(level, owner, random, origin);
}
}
private static String describeFeature(Registry<PlacedFeature> registry, PlacedFeature feature) {
Identifier id = registry.getKey(feature);
return id == null ? feature.toString() : id.toString();
}
private Set<Holder<Biome>> chunkBiomes(
WorldGenLevel level,
SectionPos sectionPos,
FeatureTable table,
DimensionStackContext stackContext
) {
List<Holder<Biome>> collected = new ArrayList<>();
ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach((ChunkPos chunkPos) -> {
ChunkAccess neighbour = level.getChunk(chunkPos.x(), chunkPos.z());
LevelChunkSection[] sections = neighbour.getSections();
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
if (stackContext == null) {
section.getBiomes().getAll(collected::add);
continue;
}
int sectionMinimumY = neighbour.getSectionYFromSectionIndex(sectionIndex) << 4;
for (int quartY = 0; quartY < 4; quartY++) {
int internalY = sectionMinimumY + (quartY << 2) - level.getMinY();
for (int quartX = 0; quartX < 4; quartX++) {
int blockX = chunkPos.getMinBlockX() + (quartX << 2);
for (int quartZ = 0; quartZ < 4; quartZ++) {
int blockZ = chunkPos.getMinBlockZ() + (quartZ << 2);
DimensionStackLayout.Layer layer = stackContext.getLayerAt(
blockX, internalY, blockZ);
if (layer.terrainContext().isSelfReferencing()) {
collected.add(section.getBiomes().get(quartX, quartY, quartZ));
}
}
}
}
}
});
Set<Holder<Biome>> present = new LinkedHashSet<>();
for (Holder<Biome> biome : collected) {
if (table.biomeSet().contains(biome)) {
present.add(biome);
continue;
}
String key = holderKey(biome);
Holder<Biome> canonical = key == null ? null : table.byKey().get(key);
if (canonical != null) {
present.add(canonical);
}
}
return present;
}
private static void addKey(Set<String> keys, String key) {
String normalized = normalizeKey(key);
if (normalized != null) {
keys.add(normalized);
}
}
private String dimensionKey() {
IrisDimension dimension = engine.getDimension();
return dimension == null ? "<unbound>" : dimension.getLoadKey();
}
private static String holderKey(Holder<Biome> holder) {
return holder.unwrapKey()
.map(key -> key.identifier().toString().toLowerCase(Locale.ROOT))
.orElse(null);
}
private static String normalizeKey(String key) {
Identifier identifier = key == null ? null : Identifier.tryParse(key);
return identifier == null ? null : identifier.toString().toLowerCase(Locale.ROOT);
}
private record FeatureTable(IrisDimension dimension, IrisImportedFeatureControl control,
List<Holder<Biome>> biomes, Set<Holder<Biome>> biomeSet,
Map<String, Holder<Biome>> byKey,
List<FeatureSorter.StepFeatureData> steps,
Map<String, Holder<Biome>> derivatives, boolean filtered) {
}
private record RuntimeFeatureState(IrisDimension dimension, FeatureTable featureTable) {
}
}
@@ -0,0 +1,142 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.moonrise.common.util.TickThread;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import net.minecraft.world.entity.ai.village.poi.PoiSection;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import java.util.Arrays;
import java.util.Optional;
final class NativeStructurePoiUpdates {
private static final NamespacedKey DIRTY_SECTIONS = new NamespacedKey("iris", "native_poi_sections");
private final int skipPoiFlag;
NativeStructurePoiUpdates(int skipPoiFlag) {
this.skipPoiFlag = skipPoiFlag;
}
static NativeStructurePoiUpdates create() {
try {
return new NativeStructurePoiUpdates(Block.class.getField("UPDATE_SKIP_POI").getInt(null));
} catch (NoSuchFieldException e) {
return null;
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
boolean setBlock(WorldGenLevel world, BlockPos position, BlockState state, int flags, int limit) {
if ((flags & skipPoiFlag) != 0) {
return world.setBlock(position, state, flags, limit);
}
ChunkAccess chunk = world.getChunk(position);
if (!(chunk instanceof ProtoChunk)) {
return world.setBlock(position, state, flags, limit);
}
if (!(chunk instanceof ImposterProtoChunk)
&& !PoiTypes.forState(chunk.getBlockState(position)).equals(PoiTypes.forState(state))) {
markSection(chunk, chunk.getSectionIndex(position.getY()));
}
return world.setBlock(position, state, flags | skipPoiFlag, limit);
}
static void reconcile(LevelChunk chunk) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
if (sections == null) {
return;
}
validateSections(chunk, sections);
ServerLevel world = (ServerLevel) chunk.getLevel();
TickThread.ensureTickThread(world, chunk.getPos(), "Native structure POIs require the chunk owning thread");
PoiManager manager = world.getPoiManager();
for (int word = 0; word < sections.length; word++) {
long remaining = sections[word];
while (remaining != 0) {
int index = (word << 6) + Long.numberOfTrailingZeros(remaining);
reconcileSection(chunk, manager, index);
remaining &= remaining - 1;
}
}
chunk.persistentDataContainer.remove(DIRTY_SECTIONS);
chunk.markUnsaved();
}
private static void markSection(ChunkAccess chunk, int index) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
int word = index >>> 6;
long bit = 1L << (index & 63);
if (sections == null) {
sections = new long[word + 1];
} else {
validateSections(chunk, sections);
if (word < sections.length && (sections[word] & bit) != 0) {
return;
}
if (word >= sections.length) {
sections = Arrays.copyOf(sections, word + 1);
}
}
sections[word] |= bit;
chunk.persistentDataContainer.set(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY, sections);
}
private static void validateSections(ChunkAccess chunk, long[] sections) {
int count = chunk.getSectionsCount();
int words = (count + 63) >>> 6;
if (sections.length > words
|| sections.length == words && (count & 63) != 0
&& (sections[words - 1] >>> (count & 63)) != 0) {
throw new IllegalArgumentException("Native POI repair sections exceed chunk height at " + chunk.getPos());
}
}
private static void reconcileSection(LevelChunk chunk, PoiManager manager, int index) {
SectionPos sectionPosition = SectionPos.of(chunk.getPos(), chunk.getSectionYFromSectionIndex(index));
Optional<PoiSection> existing = manager.getOrLoad(sectionPosition.asLong());
if (existing.isPresent()) {
for (PoiRecord record : existing.get().getRecords(type -> true, PoiManager.Occupancy.ANY).toList()) {
if (!PoiTypes.forState(chunk.getBlockState(record.getPos())).equals(Optional.of(record.getPoiType()))) {
manager.remove(record.getPos());
}
}
}
LevelChunkSection section = chunk.getSection(index);
if (!section.maybeHas(PoiTypes::hasPoi)) {
return;
}
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
Optional<Holder<PoiType>> type = PoiTypes.forState(section.getBlockState(x, y, z));
if (type.isEmpty()) {
continue;
}
position.set(sectionPosition.minBlockX() + x, sectionPosition.minBlockY() + y,
sectionPosition.minBlockZ() + z);
if (!manager.getType(position).equals(type)) {
manager.add(position.immutable(), type.get());
}
}
}
}
}
}
@@ -0,0 +1,606 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.attribute.EnvironmentAttributeReader;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.IntBinaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
final class NativeStructureWorldgenAccess implements WorldGenLevel {
private static final int WRITE_RADIUS = 1;
private static final NativeStructurePoiUpdates POI_UPDATES = NativeStructurePoiUpdates.create();
private final WorldGenLevel delegate;
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final boolean virtualizeHeights;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
private final AABB generationBounds;
private final LevelTickAccess<Block> blockTicks;
private final LevelTickAccess<Fluid> fluidTicks;
private final Long2LongOpenHashMap terrainHeights;
private final Long2ObjectOpenHashMap<ChunkAccess> outsideChunks;
private NativeStructureWorldgenAccess(WorldGenLevel delegate, Boundary boundary) {
this.delegate = Objects.requireNonNull(delegate, "Native structure world access requires a delegate");
this.generationCenter = Objects.requireNonNull(
boundary.generationCenter(), "Native structure world access requires a generation center");
this.surfaceFirstFreeY = Objects.requireNonNull(
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.virtualizeHeights = boundary.virtualizeHeights();
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
this.fallbackBiome = delegate.getBiome(biomeSample);
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
this.generationBounds = new AABB(
generationCenter.getMinBlockX() - 16,
delegate.getMinY(),
generationCenter.getMinBlockZ() - 16,
generationCenter.getMaxBlockX() + 17,
delegate.getMinY() + delegate.getHeight(),
generationCenter.getMaxBlockZ() + 17);
this.blockTicks = new BoundedTickAccess<>(delegate.getBlockTicks(), this::isWritable);
this.fluidTicks = new BoundedTickAccess<>(delegate.getFluidTicks(), this::isWritable);
this.terrainHeights = new Long2LongOpenHashMap();
this.terrainHeights.defaultReturnValue(Long.MIN_VALUE);
this.outsideChunks = new Long2ObjectOpenHashMap<>();
}
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
return new NativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY, virtualizeHeights, protectedPosition));
}
@Override
public long getSeed() {
return delegate.getSeed();
}
@Override
public boolean ensureCanWrite(BlockPos position) {
return isWritable(position) && delegate.ensureCanWrite(position);
}
@Override
public void setCurrentlyGenerating(Supplier<String> description) {
delegate.setCurrentlyGenerating(description);
}
@Override
public ServerLevel getLevel() {
return delegate.getLevel();
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos position) {
if (isReadable(position)) {
return delegate.getCurrentDifficultyAt(position);
}
return delegate.getCurrentDifficultyAt(generationCenter.getMiddleBlockPosition(
Mth.clamp(position.getY(), getMinY(), getMaxY() - 1)));
}
@Override
public long nextSubTickCount() {
return delegate.nextSubTickCount();
}
@Override
public LevelTickAccess<Block> getBlockTicks() {
return blockTicks;
}
@Override
public LevelTickAccess<Fluid> getFluidTicks() {
return fluidTicks;
}
@Override
public LevelData getLevelData() {
return delegate.getLevelData();
}
@Override
public MinecraftServer getServer() {
return delegate.getServer();
}
@Override
public ChunkSource getChunkSource() {
return delegate.getChunkSource();
}
@Override
public RandomSource getRandom() {
return delegate.getRandom();
}
@Override
public void updateNeighborsAt(BlockPos position, Block block) {
if (isWritableNeighbourhood(position)) {
delegate.updateNeighborsAt(position, block);
}
}
@Override
public void neighborShapeChanged(Direction direction, BlockPos position,
BlockPos neighbourPosition, BlockState neighbourState,
int updateFlags, int updateLimit) {
if (isWritable(position) && isWritable(neighbourPosition)) {
delegate.neighborShapeChanged(
direction, position, neighbourPosition, neighbourState, updateFlags, updateLimit);
}
}
@Override
public void playSound(Entity source, BlockPos position, SoundEvent sound,
SoundSource soundSource, float volume, float pitch) {
if (isWritable(position)) {
delegate.playSound(source, position, sound, soundSource, volume, pitch);
}
}
@Override
public void addParticle(ParticleOptions particle, double x, double y, double z,
double velocityX, double velocityY, double velocityZ) {
if (isWritable(BlockPos.containing(x, y, z))) {
delegate.addParticle(particle, x, y, z, velocityX, velocityY, velocityZ);
}
}
@Override
public void levelEvent(Entity source, int eventId, BlockPos position, int data) {
if (isWritable(position)) {
delegate.levelEvent(source, eventId, position, data);
}
}
@Override
public void gameEvent(Holder<GameEvent> event, Vec3 position, GameEvent.Context context) {
if (isWritable(BlockPos.containing(position))) {
delegate.gameEvent(event, position, context);
}
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean create) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ, status, create);
}
return create ? outsideChunk(chunkX, chunkZ) : null;
}
@Override
public ChunkAccess getChunkIfLoadedImmediately(int chunkX, int chunkZ) {
if (!isInsideGenerationRegion(chunkX, chunkZ)) {
return null;
}
return delegate.getChunkIfLoadedImmediately(chunkX, chunkZ);
}
@Override
public boolean hasChunk(int chunkX, int chunkZ) {
return isInsideGenerationRegion(chunkX, chunkZ) && delegate.hasChunk(chunkX, chunkZ);
}
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
if (!virtualizeHeights && isInsideGenerationRegion(x >> 4, z >> 4)) {
return delegate.getHeight(type, x, z);
}
return height(type, x, z);
}
@Override
public int getSkyDarken() {
return delegate.getSkyDarken();
}
@Override
public BiomeManager getBiomeManager() {
return biomeManager;
}
@Override
public Holder<Biome> getNoiseBiome(int quartX, int quartY, int quartZ) {
return getUncachedNoiseBiome(quartX, quartY, quartZ);
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
if (isInsideGenerationRegion(QuartPos.toSection(quartX), QuartPos.toSection(quartZ))) {
return delegate.getUncachedNoiseBiome(quartX, quartY, quartZ);
}
return fallbackBiome;
}
@Override
public boolean isClientSide() {
return delegate.isClientSide();
}
@Override
public int getSeaLevel() {
return delegate.getSeaLevel();
}
@Override
public DimensionType dimensionType() {
return delegate.dimensionType();
}
@Override
public int getMinY() {
return delegate.getMinY();
}
@Override
public int getHeight() {
return delegate.getHeight();
}
@Override
public RegistryAccess registryAccess() {
return delegate.registryAccess();
}
@Override
public FeatureFlagSet enabledFeatures() {
return delegate.enabledFeatures();
}
@Override
public EnvironmentAttributeReader environmentAttributes() {
return delegate.environmentAttributes();
}
@Override
public LevelLightEngine getLightEngine() {
return delegate.getLightEngine();
}
@Override
public int getBrightness(LightLayer layer, BlockPos position) {
if (isReadable(position)) {
return delegate.getBrightness(layer, position);
}
return layer == LightLayer.SKY && canSeeSky(position) ? 15 : 0;
}
@Override
public int getRawBrightness(BlockPos position, int ambientDarkening) {
if (isReadable(position)) {
return delegate.getRawBrightness(position, ambientDarkening);
}
return Math.max(0, getBrightness(LightLayer.SKY, position) - ambientDarkening);
}
@Override
public boolean canSeeSky(BlockPos position) {
if (isReadable(position)) {
return delegate.canSeeSky(position);
}
return position.getY() >= height(Heightmap.Types.WORLD_SURFACE_WG, position.getX(), position.getZ());
}
@Override
public WorldBorder getWorldBorder() {
return delegate.getWorldBorder();
}
@Override
public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunkForCollisions(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public BlockEntity getBlockEntity(BlockPos position) {
return isWritable(position) ? delegate.getBlockEntity(position) : null;
}
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(
BlockPos position, BlockEntityType<T> type) {
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
}
@Override
public BlockState getBlockState(BlockPos position) {
return isReadable(position) ? delegate.getBlockState(position) : terrainState(position);
}
@Override
public BlockState getBlockStateIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getBlockStateIfLoaded(position) : null;
}
@Override
public FluidState getFluidState(BlockPos position) {
return isReadable(position) ? delegate.getFluidState(position) : terrainState(position).getFluidState();
}
@Override
public FluidState getFluidIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getFluidIfLoaded(position) : null;
}
@Override
public List<Entity> getEntities(Entity source, AABB area, Predicate<? super Entity> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(source, boundedArea, predicate);
}
@Override
public <T extends Entity> List<T> getEntities(
EntityTypeTest<Entity, T> type,
AABB area, Predicate<? super T> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(type, boundedArea, predicate);
}
@Override
public List<? extends Player> players() {
return delegate.players();
}
@Override
public boolean isStateAtPosition(BlockPos position, Predicate<BlockState> predicate) {
return predicate.test(getBlockState(position));
}
@Override
public boolean isFluidAtPosition(BlockPos position, Predicate<FluidState> predicate) {
return predicate.test(getFluidState(position));
}
@Override
public BlockPos getHeightmapPos(Heightmap.Types type, BlockPos position) {
return position.atY(getHeight(type, position.getX(), position.getZ()));
}
@Override
public boolean setBlock(BlockPos position, BlockState state, int updateFlags, int updateLimit) {
return isWritable(position)
&& (POI_UPDATES != null
? POI_UPDATES.setBlock(delegate, position, state, updateFlags, updateLimit)
: delegate.setBlock(position, state, updateFlags, updateLimit));
}
@Override
public boolean removeBlock(BlockPos position, boolean move) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, Block.UPDATE_LIMIT)
: delegate.removeBlock(position, move);
}
@Override
public boolean destroyBlock(BlockPos position, boolean drop, Entity source, int updateLimit) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? !delegate.getBlockState(position).isAir()
&& setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, updateLimit)
: delegate.destroyBlock(position, drop, source, updateLimit);
}
@Override
public boolean addFreshEntity(Entity entity) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity);
}
@Override
public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity, reason);
}
private boolean isReadable(BlockPos position) {
return isInsideGenerationRegion(position.getX() >> 4, position.getZ() >> 4)
&& !isOutsideBuildHeight(position);
}
private boolean isWritable(BlockPos position) {
return isReadable(position) && !protectedPosition.test(position);
}
private boolean isWritableNeighbourhood(BlockPos position) {
return isWritable(position)
&& isWritable(position.north())
&& isWritable(position.south())
&& isWritable(position.east())
&& isWritable(position.west())
&& isWritable(position.above())
&& isWritable(position.below());
}
private AABB boundedArea(AABB area) {
double minX = Math.max(area.minX, generationBounds.minX);
double minY = Math.max(area.minY, generationBounds.minY);
double minZ = Math.max(area.minZ, generationBounds.minZ);
double maxX = Math.min(area.maxX, generationBounds.maxX);
double maxY = Math.min(area.maxY, generationBounds.maxY);
double maxZ = Math.min(area.maxZ, generationBounds.maxZ);
if (minX >= maxX || minY >= maxY || minZ >= maxZ) {
return null;
}
if (minX == area.minX && minY == area.minY && minZ == area.minZ
&& maxX == area.maxX && maxY == area.maxY && maxZ == area.maxZ) {
return area;
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
boolean isInsideGenerationRegion(int chunkX, int chunkZ) {
return Math.abs(chunkX - generationCenter.x()) <= WRITE_RADIUS
&& Math.abs(chunkZ - generationCenter.z()) <= WRITE_RADIUS;
}
@Override
public boolean isOutsideBuildHeight(BlockPos position) {
return position.getY() < getMinY() || position.getY() >= getMaxY();
}
private BlockState terrainState(BlockPos position) {
if (isOutsideBuildHeight(position)) {
return Blocks.VOID_AIR.defaultBlockState();
}
long heights = heights(position.getX(), position.getZ());
int surface = (int) (heights >> 32);
int floor = (int) heights;
if (position.getY() < floor) {
return Blocks.STONE.defaultBlockState();
}
if (position.getY() < surface) {
return Blocks.WATER.defaultBlockState();
}
return Blocks.AIR.defaultBlockState();
}
private int height(Heightmap.Types type, int x, int z) {
long heights = heights(x, z);
if (type == Heightmap.Types.OCEAN_FLOOR || type == Heightmap.Types.OCEAN_FLOOR_WG) {
return (int) heights;
}
return (int) (heights >> 32);
}
private long heights(int x, int z) {
long key = ((long) x << 32) ^ (z & 0xffffffffL);
long cached = terrainHeights.get(key);
if (cached != Long.MIN_VALUE) {
return cached;
}
int floor = Mth.clamp(floorFirstFreeY.applyAsInt(x, z), getMinY(), getMaxY());
int surface = Mth.clamp(surfaceFirstFreeY.applyAsInt(x, z), floor, getMaxY());
long heights = ((long) surface << 32) | (floor & 0xffffffffL);
terrainHeights.put(key, heights);
return heights;
}
private ChunkAccess outsideChunk(int chunkX, int chunkZ) {
long key = ChunkPos.pack(chunkX, chunkZ);
ChunkAccess cached = outsideChunks.get(key);
if (cached != null) {
return cached;
}
EmptyLevelChunk chunk = new EmptyLevelChunk(getLevel(), new ChunkPos(chunkX, chunkZ), fallbackBiome);
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY, floorFirstFreeY);
outsideChunks.put(key, chunk);
return chunk;
}
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
}
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
private final LevelTickAccess<T> delegate;
private final Predicate<BlockPos> writable;
private BoundedTickAccess(LevelTickAccess<T> delegate, Predicate<BlockPos> writable) {
this.delegate = delegate;
this.writable = writable;
}
@Override
public void schedule(ScheduledTick<T> tick) {
if (writable.test(tick.pos())) {
delegate.schedule(tick);
}
}
@Override
public boolean hasScheduledTick(BlockPos position, T type) {
return writable.test(position) && delegate.hasScheduledTick(position, type);
}
@Override
public int count() {
return delegate.count();
}
@Override
public boolean willTickThisTick(BlockPos position, T type) {
return writable.test(position) && delegate.willTickThisTick(position, type);
}
}
}
@@ -0,0 +1,61 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.agent.Agent;
import it.unimi.dsi.fastutil.shorts.ShortList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import java.util.function.LongPredicate;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.List;
final class NmsGenerationHooks {
private NmsGenerationHooks() {
}
static void install() {
ByteBuddy buddy = new ByteBuddy();
for (Class<?> clazz : List.of(ChunkAccess.class, ProtoChunk.class)) {
buddy.redefine(clazz)
.visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class))))
.make()
.load(clazz.getClassLoader(), Agent.installed());
}
buddy.redefine(WorldGenRegion.class)
.visit(Advice.to(WorldGenerationWriteAdvice.class).on(ElementMatchers.named("ensureCanWrite")
.and(ElementMatchers.takesArguments(BlockPos.class))))
.make()
.load(WorldGenRegion.class.getClassLoader(), Agent.installed());
}
private static class WorldGenerationWriteAdvice {
@Advice.OnMethodExit
static void exit(@Advice.This WorldGenRegion region, @Advice.Argument(0) BlockPos position,
@Advice.Return(readOnly = false) boolean allowed) {
if (!allowed) {
return;
}
ChunkGenerator generator = region.getLevel().getChunkSource().getGenerator();
if (generator instanceof LongPredicate predicate
&& generator.getClass().getName().equals("art.arcane.iris.platform.bukkit.nms.v26_2_R1.IrisChunkGenerator")
&& region.getChunk(position.getX() >> 4, position.getZ() >> 4)
.getPersistedStatus().isOrAfter(ChunkStatus.FULL)) {
allowed = predicate.test(ChunkPos.pack(position.getX() >> 4, position.getZ() >> 4));
}
}
}
private static class ChunkAccessAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) {
return index >= access.getPostProcessing().length;
}
}
}
@@ -0,0 +1,182 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
final class NmsGenerationRegistry implements PlatformGenerationRegistry {
private static final String BIOME_REGISTRY = "minecraft:worldgen/biome";
private static final String STRUCTURE_REGISTRY = "minecraft:worldgen/structure";
private static final String DIMENSION_TYPE_REGISTRY = "minecraft:dimension_type";
private static final String BLOCK_REGISTRY = "minecraft:block";
private static final String ENTITY_TYPE_REGISTRY = "minecraft:entity_type";
private final Supplier<RegistryAccess> registryAccess;
private final String runtimeIdentity;
private final String rendererIdentity;
NmsGenerationRegistry(
Supplier<RegistryAccess> registryAccess,
String runtimeIdentity,
String rendererIdentity
) {
this.registryAccess = Objects.requireNonNull(registryAccess, "registryAccess");
this.runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity");
this.rendererIdentity = requireText(rendererIdentity, "rendererIdentity");
}
@Override
public String runtimeIdentity() {
return runtimeIdentity;
}
@Override
public String generatedDefinitionRendererIdentity() {
return rendererIdentity;
}
@Override
public String customBiomeResourceKey(String identitySha256) {
return PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(identitySha256);
}
@Override
public List<String> legacyCustomBiomeResourceKeys(
String packName,
String dimensionKey,
String customBiomeId
) {
return List.of(requireText(dimensionKey, "dimensionKey").toLowerCase(Locale.ROOT)
+ ":" + requireText(customBiomeId, "customBiomeId").toLowerCase(Locale.ROOT));
}
@Override
public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) {
return "iris:" + requireText(dimensionTypeKey, "dimensionTypeKey").toLowerCase(Locale.ROOT);
}
@Override
public Definition definition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> presence(access, Registries.BIOME, registryKey, resourceKey);
case STRUCTURE_REGISTRY -> presence(access, Registries.STRUCTURE, registryKey, resourceKey);
case DIMENSION_TYPE_REGISTRY -> presence(
access,
Registries.DIMENSION_TYPE,
registryKey,
resourceKey
);
case BLOCK_REGISTRY -> presence(access, Registries.BLOCK, registryKey, resourceKey);
case ENTITY_TYPE_REGISTRY -> presence(access, Registries.ENTITY_TYPE, registryKey, resourceKey);
default -> throw new IllegalArgumentException("Unsupported generation registry: " + registryKey);
};
}
@Override
public Definition canonicalDefinition(String registryKey, String resourceKey, String sourceJson) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> canonicalize(access, Biome.DIRECT_CODEC, sourceJson);
case DIMENSION_TYPE_REGISTRY -> canonicalize(access, DimensionType.DIRECT_CODEC, sourceJson);
default -> throw new IllegalArgumentException(
"Registry does not accept generated JSON definitions: " + registryKey
);
};
}
@Override
public Definition generatedDefinition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> exactRegistered(
access,
Registries.BIOME,
Biome.DIRECT_CODEC,
resourceKey
);
case DIMENSION_TYPE_REGISTRY -> exactRegistered(
access,
Registries.DIMENSION_TYPE,
DimensionType.DIRECT_CODEC,
resourceKey
);
default -> throw new IllegalArgumentException(
"Registry does not contain Iris-generated definitions: " + registryKey
);
};
}
private static <T> Definition presence(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
String registryResourceKey,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing registry definition " + registryKey.identifier()
+ " / " + resourceKey + ".");
}
return Definition.resourceIdentity(registryResourceKey, resourceKey);
}
private static <T> Definition canonicalize(RegistryAccess access, Codec<T> codec, String sourceJson) {
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement source = JsonParser.parseString(Objects.requireNonNull(sourceJson, "sourceJson"));
T decoded = codec.parse(ops, source).getOrThrow(IllegalArgumentException::new);
JsonElement encoded = codec.encodeStart(ops, decoded).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static <T> Definition exactRegistered(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
Codec<T> codec,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing generated registry definition "
+ registryKey.identifier() + " / " + resourceKey + ".");
}
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement encoded = codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static Identifier requireIdentifier(String resourceKey) {
Identifier identifier = Identifier.tryParse(resourceKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registry resource key: " + resourceKey);
}
return identifier;
}
private static String requireText(String value, String label) {
String required = Objects.requireNonNull(value, label).trim();
if (required.isEmpty()) {
throw new IllegalArgumentException(label + " must not be blank.");
}
return required;
}
}
@@ -0,0 +1,266 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import art.arcane.iris.world.task.J;
import ca.spottedleaf.concurrentutil.completable.Completable;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
final class NmsSavedTerrainCapture {
private static final long WRITE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(30L);
private static final int SNAPSHOT_BATCH_SIZE = 64;
private NmsSavedTerrainCapture() {
}
static CompletableFuture<SavedTerrainChunk> capture(World world, int chunkX, int chunkZ, int minimumY, int height) {
ServerLevel level = ((CraftWorld) world).getHandle();
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
J.runRegionFuture(world, chunkX, chunkZ, () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
if (chunk == null) {
snapshot.complete(new CapturedChunk(new ChunkPos(chunkX, chunkZ), CompletableFuture.completedFuture(null)));
return;
}
String status = BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString();
if (!SavedTerrainChunk.hasTerrain(status)) {
throw new IOException("Native chunk " + chunkX + "," + chunkZ + " has no completed terrain at " + status);
}
snapshot.complete(scheduleSnapshot(level, chunk));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
return snapshot.thenApplyAsync(captured -> {
try {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
CompoundTag data = awaitSnapshot(level, captured, deadline);
Checkpoint checkpoint = checkpoint(chunkX, chunkZ, data);
awaitWrite(level, checkpoint.chunk(), deadline);
if (data == null) {
return SavedTerrainChunk.read(world.getWorldFolder().toPath(), chunkX, chunkZ, minimumY, height);
}
verifyCheckpoint(world.getWorldFolder().toPath(), checkpoint);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(bytes)) {
NbtIo.write(data, output);
}
return SavedTerrainChunk.readNbt(bytes.toByteArray(), chunkX, chunkZ, minimumY, height);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static CompletableFuture<Void> flush(World world) {
ServerLevel level = ((CraftWorld) world).getHandle();
List<NewChunkHolder> holders = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolders();
return CompletableFuture.runAsync(() -> {
try {
flushSnapshots(world, level, holders);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static void flushSnapshots(World world, ServerLevel level, List<NewChunkHolder> holders) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
List<Checkpoint> checkpoints = new ArrayList<>(holders.size());
for (int offset = 0; offset < holders.size(); offset += SNAPSHOT_BATCH_SIZE) {
int end = Math.min(holders.size(), offset + SNAPSHOT_BATCH_SIZE);
List<CompletableFuture<CapturedChunk>> snapshots = requestSnapshots(world, level, holders.subList(offset, end));
awaitSnapshots(snapshots, deadline);
for (CompletableFuture<CapturedChunk> future : snapshots) {
CapturedChunk captured = future.join();
CompoundTag data = awaitSnapshot(level, captured, deadline);
checkpoints.add(checkpoint(captured.chunk().x(), captured.chunk().z(), data));
}
for (int index = offset; index < end; index++) {
awaitWrite(level, checkpoints.get(index).chunk(), deadline);
}
}
finishCheckpoint(level, world.getWorldFolder().toPath(), checkpoints);
}
private static List<CompletableFuture<CapturedChunk>> requestSnapshots(
World world, ServerLevel level, List<NewChunkHolder> holders) {
List<CompletableFuture<CapturedChunk>> snapshots = new ArrayList<>(holders.size());
for (NewChunkHolder original : holders) {
ChunkPos position = new ChunkPos(original.chunkX, original.chunkZ);
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
snapshots.add(snapshot);
J.runRegionFuture(world, position.x(), position.z(), () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager
.getChunkHolder(position.x(), position.z());
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
snapshot.complete(chunk != null && chunk.isUnsaved() ? scheduleSnapshot(level, chunk)
: new CapturedChunk(position, CompletableFuture.completedFuture(null)));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
}
return snapshots;
}
private static void awaitSnapshots(List<CompletableFuture<CapturedChunk>> snapshots, long deadlineNanos)
throws IOException {
try {
CompletableFuture.allOf(snapshots.toArray(CompletableFuture[]::new))
.get(Math.max(0L, deadlineNanos - System.nanoTime()), TimeUnit.NANOSECONDS);
} catch (InterruptedException failure) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while copying terrain checkpoint chunks.", failure);
} catch (ExecutionException | TimeoutException failure) {
throw new IOException("Unable to copy terrain checkpoint chunks.", failure);
}
}
static void finishCheckpoint(ServerLevel level, Path worldFolder, List<Checkpoint> checkpoints) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
for (Checkpoint checkpoint : checkpoints) {
awaitWrite(level, checkpoint.chunk(), deadline);
}
MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
for (Checkpoint checkpoint : checkpoints) {
verifyCheckpoint(worldFolder, checkpoint);
}
}
static Checkpoint checkpoint(int chunkX, int chunkZ, CompoundTag data) {
if (data == null) {
return new Checkpoint(new ChunkPos(chunkX, chunkZ), null);
}
byte[] receipt = data.getCompound("ChunkBukkitValues")
.flatMap(values -> values.getByteArray(NativeTerrainReceipt.NBT_KEY)).orElse(null);
long structureActivation = data.getCompound("ChunkBukkitValues").map(values -> values.getLongOr(
NativeTerrainReceipt.STRUCTURE_ACTIVATION_KEY, 0)).orElse(0L);
return new Checkpoint(new ChunkPos(chunkX, chunkZ), new Verification(
data.getStringOr("Status", ""), receipt, structureActivation));
}
private static void verifyCheckpoint(Path worldFolder, Checkpoint checkpoint) throws IOException {
Verification expected = checkpoint.verification();
if (expected == null) {
return;
}
SavedTerrainChunk.verifyCheckpoint(worldFolder, checkpoint.chunk().x(), checkpoint.chunk().z(),
expected.status(), expected.receipt(), expected.structureActivation());
}
static void awaitWrite(ServerLevel level, ChunkPos chunk, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (true) {
checkWriteWait(chunk, deadlineNanos);
if (MoonriseRegionFileIO.getPriority(level, chunk.x(), chunk.z(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA) == Priority.COMPLETING) {
return;
}
helpSaveTasks(scheduler);
}
}
static CapturedChunk scheduleSnapshot(ServerLevel level, ChunkAccess chunk) {
SerializableChunkData snapshot = SerializableChunkData.copyOf(level, chunk);
PlatformHooks.get().chunkSyncSave(level, chunk, snapshot);
Completable<CompoundTag> nativeData = new Completable<>();
CompletableFuture<CompoundTag> data = new CompletableFuture<>();
PrioritisedExecutor.PrioritisedTask task = level.moonrise$getChunkTaskScheduler().saveExecutor
.createTask(() -> serializeSnapshot(snapshot, nativeData, data));
task.queue();
ChunkPos position = chunk.getPos();
MoonriseRegionFileIO.scheduleSave(level, position.x(), position.z(), nativeData, task,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, Priority.NORMAL);
return new CapturedChunk(position, data);
}
static CompoundTag awaitSnapshot(ServerLevel level, CapturedChunk captured, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (!captured.data().isDone()) {
checkWriteWait(captured.chunk(), deadlineNanos);
helpSaveTasks(scheduler);
}
try {
return captured.data().join();
} catch (CompletionException failure) {
throw new IOException("Unable to serialize terrain checkpoint chunk " + captured.chunk(), failure.getCause());
}
}
private static void serializeSnapshot(SerializableChunkData snapshot, Completable<CompoundTag> nativeData,
CompletableFuture<CompoundTag> data) {
try {
CompoundTag serialized = snapshot.write();
nativeData.complete(serialized);
data.complete(serialized);
} catch (Throwable failure) {
nativeData.completeExceptionally(failure);
data.completeExceptionally(failure);
}
}
private static void checkWriteWait(ChunkPos chunk, long deadlineNanos) throws IOException {
if (Thread.currentThread().isInterrupted()) {
throw new IOException("Interrupted while saving terrain checkpoint chunk " + chunk);
}
if (System.nanoTime() - deadlineNanos >= 0L) {
throw new IOException("Timed out saving terrain checkpoint chunk " + chunk);
}
}
private static void helpSaveTasks(ChunkTaskScheduler scheduler) {
boolean helped = scheduler.saveExecutor.executeTask();
helped |= scheduler.compressionExecutor.executeTask();
if (!helped) {
LockSupport.parkNanos(1_000_000L);
}
}
record CapturedChunk(ChunkPos chunk, CompletableFuture<CompoundTag> data) {
}
record Checkpoint(ChunkPos chunk, Verification verification) {
}
record Verification(String status, byte[] receipt, long structureActivation) {
}
}
@@ -0,0 +1,403 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.bukkit.nms.ServerShutdownBoundary;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.agent.Agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
final class NmsWorldLifecycle {
private final AtomicBoolean injected = new AtomicBoolean();
private volatile ResettableClassFileTransformer levelStorageAccessTransformer;
private volatile ResettableClassFileTransformer serverLevelTransformer;
private volatile ResettableClassFileTransformer pluginClassLoaderTransformer;
private boolean pluginClassLoaderCloseDeferred;
public boolean injectBukkit() {
synchronized (injected) {
if (injected.get()) {
return true;
}
try {
IrisLogging.info("Injecting Bukkit");
Agent.requireClassLoaderCloseDeferral();
Class<?> loaderCloseType = getClass().getClassLoader().getClass()
.getMethod("close").getDeclaringClass();
PluginClassLoaderInjectionListener loaderListener = new PluginClassLoaderInjectionListener();
pluginClassLoaderTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
.with(loaderListener)
.type(ElementMatchers.is(loaderCloseType))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(PluginClassLoaderCloseAdvice.class)
.on(ElementMatchers.named("close")
.and(ElementMatchers.takesArguments(0))
.and(ElementMatchers.returns(void.class)))))
.installOn(Agent.getInstrumentation());
loaderListener.requireInstalled();
levelStorageAccessTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(LevelStorageSource.LevelStorageAccess.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(LevelStorageAccessAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArguments(4))
.and(ElementMatchers.takesArgument(0, LevelStorageSource.class))
.and(ElementMatchers.takesArgument(1, String.class))
.and(ElementMatchers.takesArgument(2, Path.class))
.and(ElementMatchers.takesArgument(3, ResourceKey.class)))))
.installOn(Agent.getInstrumentation());
serverLevelTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(ServerLevel.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArgument(0, MinecraftServer.class))
.and(ElementMatchers.takesArgument(5, LevelStem.class)))))
.installOn(Agent.getInstrumentation());
NmsGenerationHooks.install();
injected.set(true);
return true;
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
ResettableClassFileTransformer partialPluginClassLoader = pluginClassLoaderTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
pluginClassLoaderTransformer = null;
for (ResettableClassFileTransformer partial : new ResettableClassFileTransformer[]{
partialServerLevel,
partialStorageAccess,
partialPluginClassLoader
}) {
if (partial == null) {
continue;
}
try {
partial.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable cleanupFailure) {
IrisLogging.reportError("Failed to remove partial Bukkit lifecycle injection", cleanupFailure);
}
}
return false;
}
}
}
public void ensureServerLevelInjection() {
if (!injected.get()) {
throw new IllegalStateException("Iris world lifecycle injection is unavailable. Fix the Java Agent or Code Injection startup failure and restart before creating worlds.");
}
try {
Agent.getInstrumentation().retransformClasses(
LevelStorageSource.LevelStorageAccess.class,
ServerLevel.class
);
} catch (Throwable e) {
throw new IllegalStateException("Failed to re-apply Bukkit world lifecycle injection. World creation was stopped before loading the world.", e);
}
}
public void uninjectBukkit() {
synchronized (injected) {
ResettableClassFileTransformer activeServerLevel = serverLevelTransformer;
ResettableClassFileTransformer activeStorageAccess = levelStorageAccessTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
injected.set(false);
for (ResettableClassFileTransformer transformer : new ResettableClassFileTransformer[]{
activeServerLevel,
activeStorageAccess
}) {
if (transformer == null) {
continue;
}
try {
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
}
}
}
}
public boolean isServerStopping() {
return ((CraftServer) Bukkit.getServer()).getServer().hasStopped();
}
public ServerShutdownBoundary createServerShutdownBoundary() {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
return new ServerShutdownBoundary(
() -> server.hasFullyShutdown,
server.getRunningThread()
);
}
public void deferPluginClassLoaderClose() {
synchronized (injected) {
if (pluginClassLoaderTransformer == null || pluginClassLoaderCloseDeferred) {
return;
}
Agent.retainClassLoader(getClass().getClassLoader());
pluginClassLoaderCloseDeferred = true;
}
}
public void releasePluginClassLoaderClose() {
ResettableClassFileTransformer transformer;
boolean releaseLoader;
synchronized (injected) {
transformer = pluginClassLoaderTransformer;
pluginClassLoaderTransformer = null;
releaseLoader = pluginClassLoaderCloseDeferred;
pluginClassLoaderCloseDeferred = false;
}
try {
if (transformer != null
&& !transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)) {
throw new IllegalStateException("Failed to remove Iris plugin class loader lifecycle injection");
}
} finally {
if (releaseLoader) {
Agent.releaseClassLoader(getClass().getClassLoader());
}
}
}
private static final class PluginClassLoaderInjectionListener extends AgentBuilder.Listener.Adapter {
private volatile boolean transformed;
private volatile Throwable failure;
@Override
public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
transformed = true;
}
@Override
public void onError(String typeName, ClassLoader loader, JavaModule module,
boolean loaded, Throwable throwable) {
failure = throwable;
}
private void requireInstalled() {
if (!transformed || failure != null) {
throw new IllegalStateException("Iris plugin class loader lifecycle injection failed", failure);
}
}
}
private static class PluginClassLoaderCloseAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ClassLoader loader) throws ReflectiveOperationException {
Class<?> installer = Class.forName(
"art.arcane.iris.platform.agent.Installer", true, ClassLoader.getSystemClassLoader());
return Boolean.TRUE.equals(installer.getMethod("deferClassLoaderClose", ClassLoader.class)
.invoke(null, loader));
}
}
private static class LevelStorageAccessAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(1) String levelId,
@Advice.Argument(value = 3, readOnly = false) ResourceKey<LevelStem> dimensionType
) {
if (levelId == null || levelId.isBlank()) {
return;
}
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
return;
}
Object generator;
try {
generator = stagingType
.getDeclaredMethod("peekStemGenerator", String.class)
.invoke(null, levelId);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to inspect the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (generator == null || !generatorType.isInstance(generator)) {
return;
}
try {
Object target = generatorType.getMethod("getTarget").invoke(generator);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
dimensionType = ResourceKey.create(Registries.LEVEL_STEM, worldIdentifier);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to bind the staged world storage identity",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(0) MinecraftServer server,
@Advice.Argument(value = 4, readOnly = false) ResourceKey<Level> dimensionKey,
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
@Advice.AllArguments Object[] constructorArguments
) {
if (dimensionKey == null)
return;
// This advice is inlined into every ServerLevel construction on the server. Until a
// world is proven Iris-owned, every failure must fail OPEN (keep the vanilla stem):
// Iris being unloaded or half-loaded must never break other plugins' world creation.
String levelId;
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
levelId = dimensionKey.identifier().getPath();
if (levelId == null || levelId.isBlank()) {
return;
}
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
// Iris absent or half-loaded: fail OPEN for a world we cannot prove ours.
return;
}
// From here Iris is present and its classes resolve; a failure resolving the
// staged generator must fail LOUD — silently handing a possibly-staged Iris world
// the vanilla stem corrupts generation.
ChunkGenerator gen = null;
try {
ChunkGenerator constructorGenerator = null;
for (Object argument : constructorArguments) {
if (argument instanceof ChunkGenerator candidate) {
constructorGenerator = candidate;
break;
}
}
Object generator = generatorType.isInstance(constructorGenerator) ? constructorGenerator : null;
if (generator == null) {
generator = stagingType
.getDeclaredMethod("consumeStemGenerator", String.class)
.invoke(null, levelId);
}
if (generator instanceof ChunkGenerator owned && owned.getClass().getPackageName().startsWith("art.arcane.iris")) {
gen = owned;
}
} catch (Throwable e) {
throw new RuntimeException("Iris failed to resolve the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (gen == null) {
return;
}
// Past the ownership gate the world is Iris-owned; silently handing back the
// vanilla stem would corrupt generation, so failures from here rethrow.
try {
Object bindings = Class.forName("art.arcane.iris.platform.bukkit.nms.INMS", true, pluginClassLoader)
.getDeclaredMethod("get")
.invoke(null);
if (bindings == null) {
throw new IllegalStateException("Iris failed to resolve an INMSBinding instance.");
}
Method stemMethod = null;
for (Method candidate : bindings.getClass().getMethods()) {
if (candidate.getName().equals("createRuntimeLevelStem") && candidate.getParameterCount() == 2) {
stemMethod = candidate;
break;
}
}
if (stemMethod == null) {
throw new IllegalStateException("Iris binding is missing createRuntimeLevelStem.");
}
Object target = generatorType.getMethod("getTarget").invoke(gen);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
Object resolvedStem = stemMethod.invoke(bindings, server.registryAccess(), gen);
if (!(resolvedStem instanceof LevelStem runtimeStem)) {
throw new IllegalStateException("Iris runtime LevelStem binding returned " + (resolvedStem == null ? "null" : resolvedStem.getClass().getName()) + ".");
}
dimensionKey = ResourceKey.create(Registries.DIMENSION, worldIdentifier);
levelStem = runtimeStem;
} catch (Throwable e) {
throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
}
@@ -0,0 +1,80 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.NativeStructureGenerationKeys;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.structure.Structure;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
final class VanillaStructureBiomes {
private VanillaStructureBiomes() {
}
static Set<String> possibleBiomeKeys(BiomeSource source) {
if (source == null) {
throw new IllegalStateException("Minecraft chunk generator has no biome source");
}
Set<String> keys = new LinkedHashSet<>();
Set<Holder<Biome>> possibleBiomes = source instanceof CustomBiomeSource customBiomeSource
? customBiomeSource.possibleStructureBiomes()
: source.possibleBiomes();
for (Holder<Biome> holder : possibleBiomes) {
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
if (keys.isEmpty()) {
throw new IllegalStateException("Minecraft biome source exposes no registered possible biomes");
}
return keys;
}
static Set<String> structureBiomeKeys(RegistryAccess access, String structureKey) {
if (access == null) {
throw new IllegalStateException("Minecraft registry access is unavailable");
}
if (structureKey == null || structureKey.isBlank()) {
throw new IllegalArgumentException("Registered structure key must not be blank");
}
Set<String> keys = new LinkedHashSet<>();
Registry<Structure> registry = access.lookupOrThrow(Registries.STRUCTURE);
Structure structure = registry.getValue(Identifier.parse(structureKey));
if (structure == null) {
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
}
boolean hasFilterEntries = false;
for (Holder<Biome> holder : structure.biomes()) {
hasFilterEntries = true;
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
// An empty biome filter is legal datapack content (opt-in structures whose tags only
// reference absent modded biomes); the structure is unreachable, not an error state.
if (keys.isEmpty() && hasFilterEntries) {
throw new IllegalStateException("Registered structure '" + structureKey
+ "' has biome filter entries but none resolve to registered biome keys");
}
return keys;
}
static Set<String> reachableStructureKeys(ServerLevel level, BiomeSource source) {
if (level == null) {
throw new IllegalStateException("Minecraft server level is unavailable");
}
Set<String> possible = possibleBiomeKeys(source);
return NativeStructureGenerationKeys.reachable(level, possible);
}
}
@@ -0,0 +1,130 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.platform.bukkit.nms.v26_2_R1.IrisChunkGenerator;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
import art.arcane.iris.world.history.BoundaryColumnGeometry;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.levelgen.Heightmap;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public final class IrisChunkGeneratorHeightCacheTest {
@Rule
public final PlatformLeakGuard leakGuard = PlatformLeakGuard.clean();
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void repeatedNativeHeightSurvivesResolvedTerrainEvictionAndRuntimeChange() throws Exception {
Fixture fixture = fixture();
LevelHeightAccessor bounds = LevelHeightAccessor.create(-16, 32);
assertEquals(-11, fixture.generator().getBaseHeight(-1, -33, Heightmap.Types.WORLD_SURFACE, bounds, null));
when(fixture.complex().resolvedTerrainColumn(-1, -33))
.thenThrow(new IllegalStateException("Full terrain was evicted"));
for (int index = 0; index < 100; index++) {
assertEquals(-11, fixture.generator().getBaseHeight(
-1, -33, Heightmap.Types.WORLD_SURFACE, bounds, null));
}
verify(fixture.complex(), times(1)).resolvedTerrainColumn(-1, -33);
when(fixture.engine().getCacheID()).thenReturn(2);
doReturn(Optional.of(signature())).when(fixture.complex()).resolvedTerrainColumn(-1, -33);
assertEquals(-11, fixture.generator().getBaseHeight(-1, -33, Heightmap.Types.WORLD_SURFACE, bounds, null));
verify(fixture.complex(), times(2)).resolvedTerrainColumn(-1, -33);
}
@Test
public void ordinaryHeightRemainsLiveUntilResolvedTransitionTerrainExists() throws Exception {
Fixture fixture = fixture();
LevelHeightAccessor bounds = LevelHeightAccessor.create(-16, 32);
when(fixture.complex().resolvedTerrainColumn(0, 0))
.thenReturn(Optional.empty(), Optional.empty(), Optional.of(signature()));
when(fixture.engine().getHeight(0, 0, false)).thenReturn(20, 25);
assertEquals(5, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE, bounds, null));
assertEquals(10, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE, bounds, null));
assertEquals(-11, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE, bounds, null));
assertEquals(-11, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE, bounds, null));
verify(fixture.engine(), times(2)).getHeight(0, 0, false);
verify(fixture.complex(), times(3)).resolvedTerrainColumn(0, 0);
}
@Test
public void nativePredicatesAndAccessorBoundsRemainExact() throws Exception {
Fixture fixture = fixture();
LevelHeightAccessor bounds = LevelHeightAccessor.create(-16, 32);
for (int repeat = 0; repeat < 2; repeat++) {
assertEquals(-11, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE, bounds, null));
assertEquals(-13, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.OCEAN_FLOOR, bounds, null));
assertEquals(-13, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.MOTION_BLOCKING, bounds, null));
assertEquals(-14, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, bounds, null));
assertEquals(-14, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.WORLD_SURFACE,
LevelHeightAccessor.create(-16, 2), null));
assertEquals(-14, fixture.generator().getBaseHeight(0, 0, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
LevelHeightAccessor.create(-14, 3), null));
}
verify(fixture.complex(), times(6)).resolvedTerrainColumn(0, 0);
}
private static Fixture fixture() throws Exception {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
when(engine.getComplex()).thenReturn(complex);
when(engine.getCacheID()).thenReturn(1);
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
when(complex.resolvedTerrainColumn(anyInt(), anyInt())).thenReturn(Optional.of(signature()));
IrisChunkGenerator generator = mock(IrisChunkGenerator.class, CALLS_REAL_METHODS);
set(generator, "engine", engine);
set(generator, "terrainHeights", new NativeTerrainHeightCache());
return new Fixture(generator, engine, complex);
}
private static void set(IrisChunkGenerator generator, String name, Object value) throws Exception {
Field field = IrisChunkGenerator.class.getDeclaredField(name);
field.setAccessible(true);
field.set(generator, value);
}
private static TerrainBoundarySignature signature() {
BoundaryColumnGeometry geometry = BoundaryColumnGeometry.fromVoxels(-16, List.of(
new BoundaryColumnGeometry.Voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", false),
new BoundaryColumnGeometry.Voxel("minecraft:water", BoundaryColumnGeometry.Phase.FLUID, "minecraft:water", false),
new BoundaryColumnGeometry.Voxel("minecraft:oak_leaves[persistent=true]", BoundaryColumnGeometry.Phase.SOLID, "", false),
new BoundaryColumnGeometry.Voxel("minecraft:short_grass", BoundaryColumnGeometry.Phase.SOLID, "", false),
new BoundaryColumnGeometry.Voxel("minecraft:light[level=15]", BoundaryColumnGeometry.Phase.SOLID, "", false)));
return new TerrainBoundarySignature(
new TerrainBoundarySignature.Column(0, 0, 4, 4, OptionalInt.of(1), OptionalInt.empty()),
new TerrainBoundarySignature.Samples(new TerrainBoundarySignature.VerticalLayout(-16, 5, 1),
new TerrainBoundarySignature.BiomeEncoding(List.of("minecraft:plains"), new short[]{0})), geometry);
}
private record Fixture(IrisChunkGenerator generator, Engine engine, IrisComplex complex) {
}
}
@@ -0,0 +1,125 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.world.history.GenerationActivation;
import art.arcane.iris.world.history.GenerationHistory;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.object.IrisStaticObjectLayer;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Optional;
import java.util.function.Predicate;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
public final class NativeGenerationWriteGuardTest {
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void staticObjectsProtectRelativeHeightBeforeStackSampling() {
IrisStaticObjectLayer objects = mock(IrisStaticObjectLayer.class);
DimensionStackContext stack = mock(DimensionStackContext.class);
when(objects.contains(-3, 74, -7)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
assertTrue(protectedPositions.test(new BlockPos(-3, 10, -7)));
verifyNoInteractions(stack);
assertFalse(NativeGenerationWriteGuard.protectedPositions(objects, null, -64)
.test(new BlockPos(-3, 11, -7)));
}
@Test
public void emptyStaticLayerSkipsContainsAndCachesEachSignedColumn() {
IrisStaticObjectLayer objects = mock(IrisStaticObjectLayer.class);
DimensionStackContext stack = mock(DimensionStackContext.class);
DimensionStackLayout negative = mock(DimensionStackLayout.class);
DimensionStackLayout positive = mock(DimensionStackLayout.class);
when(objects.isEmpty()).thenReturn(true);
when(stack.sample(-1, -1)).thenReturn(negative);
when(stack.sample(-1, 1)).thenReturn(positive);
when(negative.isHostFeatureProtectedY(64)).thenReturn(true);
when(positive.isHostFeatureProtectedY(65)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
assertTrue(protectedPositions.test(new BlockPos(-1, 0, -1)));
assertFalse(protectedPositions.test(new BlockPos(-1, 1, -1)));
assertTrue(protectedPositions.test(new BlockPos(-1, 1, 1)));
verify(stack, times(1)).sample(-1, -1);
verify(stack, times(1)).sample(-1, 1);
verify(objects, never()).contains(anyInt(), anyInt(), anyInt());
}
@Test
public void preTerrainSavedStampDistinguishesEarlierNativeStarts() {
IrisEngine engine = mock(IrisEngine.class);
GenerationHistoryRuntimeRouter router = mock(GenerationHistoryRuntimeRouter.class);
GenerationHistory history = mock(GenerationHistory.class);
GenerationActivation active = mock(GenerationActivation.class);
when(engine.getGenerationHistoryRuntimeRouter()).thenReturn(Optional.of(router));
when(router.history()).thenReturn(history);
when(history.activeActivation()).thenReturn(active);
when(active.activationId()).thenReturn(4L);
assertTrue(NativeGenerationWriteGuard.isHistoricalStructure(engine, 3));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 0));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 4));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 5));
}
@Test
public void partialNeighborsMayFinishButCompletedHistoricalNeighborRejectsPass() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
when(engine.getComplex()).thenReturn(complex);
WorldGenLevel region = mock(WorldGenLevel.class);
ChunkAccess partial = mock(ChunkAccess.class);
when(partial.getPersistedStatus()).thenReturn(ChunkStatus.NOISE);
when(region.getChunk(anyInt(), anyInt())).thenReturn(partial);
assertTrue(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
ChunkAccess complete = mock(ChunkAccess.class);
when(complete.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
when(region.getChunk(1, -1)).thenReturn(complete);
assertFalse(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
}
@Test
public void historicalNoiseMayFinishFeaturesButNeverReplaysNoiseOrCompletedStage() {
Engine engine = mock(Engine.class);
IrisComplex complex = mock(IrisComplex.class);
when(engine.getComplex()).thenReturn(complex);
ChunkAccess chunk = mock(ChunkAccess.class);
when(chunk.getPos()).thenReturn(new ChunkPos(-2, 3));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.NOISE);
assertTrue(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.NOISE));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FEATURES);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.SPAWN));
}
}
@@ -0,0 +1,471 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.structure.jigsaw.IrisJigsawConfiguration;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Vec3i;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.FixedBiomeSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasBinding;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NativeStructureFactoryTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void genericSourceDoesNotRequireJigsawType() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
Structure configured = NativeStructureFactory.configure(
null, source, null, false, 80);
assertSame(source, configured);
}
@Test
public void jigsawOptionsFailClosedForNonJigsawSource() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
assertThrows(IllegalStateException.class, () -> NativeStructureFactory.configure(
null, source, new IrisJigsawConfiguration(), false, 80));
}
@Test
public void sourceMetadataReadsScalarAndAxisSpecificJigsawRanges() {
CompoundTag scalar = new CompoundTag();
scalar.putInt("max_distance_from_center", 80);
CompoundTag axisSpecific = new CompoundTag();
CompoundTag distance = new CompoundTag();
distance.putInt("horizontal", 96);
distance.putInt("vertical", 64);
axisSpecific.put("max_distance_from_center", distance);
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeStructureFactory.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeStructureFactory.sourceDistance(axisSpecific, "vertical"));
}
@Test
public void sourceMetadataIncludesAdjustBoundingBoxExpansion() {
Structure none = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
Structure beardBox = new SwampHutStructure(new Structure.StructureSettings.Builder(HolderSet.empty())
.terrainAdapation(TerrainAdjustment.BEARD_BOX)
.build());
assertEquals(0, NativeStructureFactory.horizontalReferenceExpansion(none));
assertEquals(12, NativeStructureFactory.horizontalReferenceExpansion(beardBox));
}
@Test
public void templatePoolSpanIncludesWeightedListsFallbacksAndRotations() {
StructurePoolElement narrow = inlineElement(30, 8, 70);
StructurePoolElement wide = inlineElement(96, 8, 12);
ListPoolElement list = new ListPoolElement(
List.of(narrow, wide, EmptyPoolElement.INSTANCE),
StructureTemplatePool.Projection.RIGID);
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:fallback");
StructureTemplatePool fallback = new StructureTemplatePool(
fallbackHolder,
List.of(Pair.of(inlineElement(110, 8, 20), 1)));
fallbackHolder.bind(fallback);
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
StructureTemplatePool start = new StructureTemplatePool(
fallbackHolder,
List.of(Pair.of(list, 5), Pair.of(EmptyPoolElement.INSTANCE, 1)));
startHolder.bind(start);
assertEquals(110, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
}
@Test
public void oversizedInlineStartTemplateReportsItsFullSpan() {
BoundPoolHolder holder = new BoundPoolHolder("test:oversized");
StructureTemplatePool pool = new StructureTemplatePool(
holder,
List.of(Pair.of(inlineElement(160, 8, 9), 1)));
holder.bind(pool);
assertEquals(160, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
}
@Test
public void sourceSpanIncludesOnlyAliasesThatCanReplaceTheStartPool() {
BoundPoolHolder emptyHolder = new BoundPoolHolder("test:empty");
StructureTemplatePool empty = new StructureTemplatePool(emptyHolder, List.of());
emptyHolder.bind(empty);
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
StructureTemplatePool start = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(20, 8, 12), 1)));
startHolder.bind(start);
BoundPoolHolder replacementHolder = new BoundPoolHolder("test:replacement");
StructureTemplatePool replacement = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(30, 8, 12), 1)));
replacementHolder.bind(replacement);
BoundPoolHolder unrelatedHolder = new BoundPoolHolder("test:unrelated");
StructureTemplatePool unrelated = new StructureTemplatePool(
emptyHolder, List.of(Pair.of(inlineElement(200, 8, 12), 1)));
unrelatedHolder.bind(unrelated);
ResourceKey<StructureTemplatePool> startKey = startHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> replacementKey = replacementHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> unrelatedKey = unrelatedHolder.unwrapKey().orElseThrow();
ResourceKey<StructureTemplatePool> childAlias = ResourceKey.create(
Registries.TEMPLATE_POOL, Identifier.parse("test:child_alias"));
List<PoolAliasBinding> aliases = List.of(
PoolAliasBinding.direct(startKey, replacementKey),
PoolAliasBinding.direct(childAlias, unrelatedKey));
Map<ResourceKey<StructureTemplatePool>, StructureTemplatePool> pools = Map.of(
replacementKey, replacement,
unrelatedKey, unrelated);
assertEquals(30, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
null, start, startKey, aliases, pools::get));
}
@Test
public void sourceStartSpanDoesNotIncludeItsFallbackPool() {
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:large_fallback");
StructureTemplatePool fallback = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(inlineElement(180, 8, 10), 1)));
fallbackHolder.bind(fallback);
BoundPoolHolder startHolder = new BoundPoolHolder("test:small_start");
StructureTemplatePool start = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(inlineElement(24, 8, 10), 1)));
startHolder.bind(start);
assertEquals(180, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
assertEquals(24, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
null, start, startHolder.unwrapKey().orElseThrow(), List.of(), ignored -> null));
}
@Test
public void customPoolElementUsesItsBoundingBoxCapability() {
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:custom_fallback");
StructureTemplatePool fallback = new StructureTemplatePool(fallbackHolder, List.of());
fallbackHolder.bind(fallback);
BoundPoolHolder poolHolder = new BoundPoolHolder("test:custom");
StructureTemplatePool pool = new StructureTemplatePool(
fallbackHolder, List.of(Pair.of(new BoundingPoolElement(37, 11), 1)));
poolHolder.bind(pool);
assertEquals(37, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
}
@Test
public void forcedGeneratorBypassesBiomeEligibilityWithoutFabricatingTerrain() {
ChunkGenerator delegate = new TestChunkGenerator();
Holder<Biome> biome = Holder.direct((Biome) null);
LevelHeightAccessor heightAccessor = LevelHeightAccessor.create(-64, 384);
ForcedStructureChunkGenerator generator = new ForcedStructureChunkGenerator(
delegate, biome);
assertEquals(63, generator.getSeaLevel());
assertEquals(64, generator.getBaseHeight(
0, 0, Heightmap.Types.WORLD_SURFACE_WG, heightAccessor, null));
NoiseColumn column = generator.getBaseColumn(
0, 0, heightAccessor, null);
assertSame(Blocks.STONE, column.getBlock(80).getBlock());
assertSame(Blocks.STONE, column.getBlock(81).getBlock());
assertNotSame(delegate.getBiomeSource(), generator.getBiomeSource());
}
@Test
public void undergroundRelocationMovesGenericPiecesAndReturnsFreshStart() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(17L), 0, 0);
StructureStart start = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(piece))
);
StructureStart relocated = NativeStructureVerticalPlacer.relocateToMinY(
start, source, -20, LevelHeightAccessor.create(-64, 384));
assertNotSame(start, relocated);
assertEquals(-20, relocated.getPieces().getFirst().getBoundingBox().minY());
assertEquals(-20, relocated.getBoundingBox().minY());
}
@Test
public void invalidGeneratedStartIsANormalSkippedCandidate() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
StructureStart valid = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(
new SwampHutPiece(RandomSource.create(23L), 0, 0)))
);
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
StructureStart.INVALID_START));
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(null));
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
new StructureStart(source, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of()))));
assertTrue(NativeStructureStartInjector.isUsableGeneratedStart(valid));
}
@Test
public void unrepresentableGeneratedContentBecomesAnUnpublishedInvalidStart() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece boundaryPiece = new SwampHutPiece(RandomSource.create(29L), 0, 0);
boundaryPiece.move(143 - boundaryPiece.getBoundingBox().maxX(), 0, 0);
StructureStart boundary = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(boundaryPiece))
);
SwampHutPiece oversizedPiece = new SwampHutPiece(RandomSource.create(31L), 0, 0);
oversizedPiece.move(144 - oversizedPiece.getBoundingBox().maxX(), 0, 0);
StructureStart oversized = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(oversizedPiece))
);
StructureStart boundaryResult = NativeStructureReferenceEnvelope.wrapForPublication(
boundary, source, 0, new IrisStructureTerrain(), "test:boundary_content");
StructureStart result = NativeStructureReferenceEnvelope.wrapForPublication(
oversized, source, 0,
new IrisStructureTerrain(),
"test:oversized_content");
assertTrue(boundaryResult.isValid());
assertSame(StructureStart.INVALID_START, result);
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(result));
assertEquals(1, oversized.getPieces().size());
}
private static final class TestChunkGenerator extends ChunkGenerator {
private TestChunkGenerator() {
super(new FixedBiomeSource(Holder.direct((Biome) null)));
}
@Override
protected MapCodec<? extends ChunkGenerator> codec() {
return MapCodec.unit(this);
}
@Override
public void applyCarvers(WorldGenRegion region, long seed, RandomState randomState,
BiomeManager biomeManager, StructureManager structureManager,
ChunkAccess chunk) {
}
@Override
public void buildSurface(WorldGenRegion level, StructureManager structureManager,
RandomState randomState, ChunkAccess protoChunk) {
}
@Override
public void spawnOriginalMobs(WorldGenRegion worldGenRegion) {
}
@Override
public int getGenDepth() {
return 384;
}
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(
Blender blender, RandomState randomState,
StructureManager structureManager, ChunkAccess centerChunk) {
return CompletableFuture.completedFuture(centerChunk);
}
@Override
public int getSeaLevel() {
return 63;
}
@Override
public int getMinY() {
return -64;
}
@Override
public int getBaseHeight(int x, int z, Heightmap.Types type,
LevelHeightAccessor heightAccessor, RandomState randomState) {
return 64;
}
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor,
RandomState randomState) {
BlockState[] states = new BlockState[heightAccessor.getHeight()];
for (int index = 0; index < states.length; index++) {
states[index] = Blocks.STONE.defaultBlockState();
}
return new NoiseColumn(heightAccessor.getMinY(), states);
}
@Override
public void addDebugScreenInfo(List<String> result, RandomState randomState,
BlockPos feetPos) {
}
}
private static StructurePoolElement inlineElement(int sizeX, int sizeY, int sizeZ) {
StructureTemplate template = new StructureTemplate();
CompoundTag tag = new CompoundTag();
ListTag size = new ListTag();
size.add(IntTag.valueOf(sizeX));
size.add(IntTag.valueOf(sizeY));
size.add(IntTag.valueOf(sizeZ));
tag.put("size", size);
template.load(BuiltInRegistries.BLOCK, tag);
return new InlinePoolElement(template);
}
private static final class InlinePoolElement extends SinglePoolElement {
private final StructureTemplate inlineTemplate;
private InlinePoolElement(StructureTemplate template) {
super(Either.right(template), Holder.direct(new StructureProcessorList(List.of())),
StructureTemplatePool.Projection.RIGID, Optional.<LiquidSettings>empty());
inlineTemplate = template;
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return inlineTemplate.getBoundingBox(
new StructurePlaceSettings().setRotation(rotation), position);
}
}
private static final class BoundingPoolElement extends StructurePoolElement {
private final int width;
private final int depth;
private BoundingPoolElement(int width, int depth) {
super(StructureTemplatePool.Projection.RIGID);
this.width = width;
this.depth = depth;
}
@Override
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
return new Vec3i(width, 1, depth);
}
@Override
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
StructureTemplateManager templateManager, BlockPos position,
Rotation rotation, RandomSource random) {
return List.of();
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return new BoundingBox(
position.getX(), position.getY(), position.getZ(),
position.getX() + width - 1, position.getY(),
position.getZ() + depth - 1);
}
@Override
public boolean place(StructureTemplateManager templateManager,
WorldGenLevel world,
StructureManager structureManager, ChunkGenerator chunkGenerator,
BlockPos position, BlockPos pivot, Rotation rotation,
BoundingBox area, RandomSource random,
LiquidSettings liquidSettings, boolean keepJigsaws) {
return true;
}
@Override
public StructurePoolElementType<?> getType() {
return StructurePoolElementType.EMPTY;
}
}
private static final class BoundPoolHolder extends Holder.Reference<StructureTemplatePool> {
private BoundPoolHolder(String key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, ResourceKey.create(Registries.TEMPLATE_POOL, Identifier.parse(key)), null);
}
private void bind(StructureTemplatePool pool) {
bindValue(pool);
}
}
}
@@ -0,0 +1,43 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.core.Vec3i;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Optional;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructureGenerationKeysContractTest {
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void frequencyGateUsesMappedAccessorAndRejectsNonPositivePlacements() {
assertFalse(NativeStructureGenerationKeys.isEnabledPlacement(placement(0.0F)));
assertFalse(NativeStructureGenerationKeys.isEnabledPlacement(placement(-0.1F)));
assertFalse(NativeStructureGenerationKeys.isEnabledPlacement(placement(Float.NaN)));
assertTrue(NativeStructureGenerationKeys.isEnabledPlacement(placement(0.25F)));
}
private static RandomSpreadStructurePlacement placement(float frequency) {
return new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
frequency,
1,
Optional.empty(),
2,
1,
RandomSpreadType.LINEAR);
}
}
@@ -0,0 +1,197 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructureLocatePersistenceTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void storageProbeScansEachCandidateOnlyOnce() {
AtomicInteger reads = new AtomicInteger();
CompoundTag chunk = new CompoundTag();
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
chunk.putString("Status", "full");
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 0);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
reads.incrementAndGet();
chunk.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
});
ChunkPos chunkPos = new ChunkPos(7, -11);
assertTrue(budget.acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
assertTrue(budget.acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
assertEquals(1, reads.get());
assertEquals(1, budget.used());
}
@Test
public void legacyStoredChunkIsDatafixedBeforeItsStartIsClassified() {
AtomicBoolean datafixed = new AtomicBoolean();
CompoundTag legacy = new CompoundTag();
legacy.putInt("DataVersion", 100);
legacy.putString("Status", "full");
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
legacy.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
},
(level, storedChunk) -> {
datafixed.set(true);
CompoundTag fixed = storedChunk.copy();
fixed.putInt("DataVersion",
SharedConstants.getCurrentVersion().dataVersion().version());
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 1);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
fixed.put("structures", structures);
return fixed;
});
assertFalse(budget.acceptsStored(
null, new ChunkPos(3, 4), "minecraft:village_plains", true));
assertTrue(datafixed.get());
}
@Test
public void partialDatafixFailureFallsBackToSelectedChunkVerification() {
CompoundTag legacy = new CompoundTag();
legacy.putInt("DataVersion", 100);
NativeStructureLocatePersistence.ProbeBudget budget =
new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
legacy.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
},
(level, storedChunk) -> {
throw new IllegalStateException("broken partial datafix");
});
assertTrue(budget.acceptsStored(
null, new ChunkPos(9, -2), "minecraft:village_plains", true));
}
@Test
public void missingStartsContainerFallsBackToSelectedChunkVerification() {
CompoundTag chunk = currentChunk();
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
assertTrue(budget.acceptsStored(
null, new ChunkPos(5, 8), "minecraft:village_plains", false));
assertTrue(budget.acceptsStored(
null, new ChunkPos(5, 8), "minecraft:village_plains", true));
}
@Test
public void presentEmptyStartsContainerRejectsTheCandidateWithoutLoading() {
CompoundTag chunk = currentChunk();
CompoundTag structures = new CompoundTag();
structures.put("starts", new CompoundTag());
chunk.put("structures", structures);
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
assertFalse(budget.acceptsStored(
null, new ChunkPos(-3, 2), "minecraft:village_plains", false));
}
@Test
public void collisionTombstoneIsRejectedForNormalAndUnexploredLocate() {
CompoundTag chunk = currentChunk();
CompoundTag tombstone = new CompoundTag();
tombstone.putString("id", "INVALID");
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", tombstone);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
ChunkPos chunkPos = new ChunkPos(12, -9);
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", false));
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
}
@Test
public void malformedNonCompoundStartIsRejectedWithoutLoading() {
CompoundTag chunk = currentChunk();
CompoundTag starts = new CompoundTag();
starts.putInt("minecraft:village_plains", 1);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
assertFalse(budget(chunk).acceptsStored(
null, new ChunkPos(1, 1), "minecraft:village_plains", false));
}
@Test
public void referencedStoredStartIsOnlyRejectedForUnexploredLocate() {
CompoundTag chunk = currentChunk();
CompoundTag start = new CompoundTag();
start.putString("id", "minecraft:village_plains");
start.putInt("references", 1);
CompoundTag starts = new CompoundTag();
starts.put("minecraft:village_plains", start);
CompoundTag structures = new CompoundTag();
structures.put("starts", starts);
chunk.put("structures", structures);
ChunkPos chunkPos = new ChunkPos(-4, -7);
assertTrue(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", false));
assertFalse(budget(chunk).acceptsStored(
null, chunkPos, "minecraft:village_plains", true));
}
private static CompoundTag currentChunk() {
CompoundTag chunk = new CompoundTag();
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
return chunk;
}
private static NativeStructureLocatePersistence.ProbeBudget budget(CompoundTag chunk) {
return new NativeStructureLocatePersistence.ProbeBudget(
512,
(chunkPos, visitor) -> {
chunk.acceptAsRoot(visitor);
return CompletableFuture.completedFuture(null);
});
}
}
@@ -0,0 +1,346 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class NativeStructureOwnershipFingerprintTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void monumentFingerprintSurvivesSourceYRegenerationWithoutSyntheticPieces() {
long seed = 8372619L;
ChunkPos origin = new ChunkPos(9, -4);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart generated = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
int alignedLocatorY = NativeStructureOwnershipFingerprint.locatorY(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
String beforeReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", wrapped);
ListTag serializedPieces = (ListTag) new PiecesContainer(wrapped.getPieces()).save(null);
PiecesContainer loadedPieces = PiecesContainer.load(serializedPieces, null);
assertEquals(1, loadedPieces.pieces().size());
assertTrue(loadedPieces.pieces().getFirst()
instanceof OceanMonumentPieces.MonumentBuilding);
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, loadedPieces);
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
String afterReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", reloaded);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
assertEquals(beforeReload, afterReload);
assertTrue(NativeStructureOwnershipFingerprint.matches(ownership, reloaded));
assertNotEquals(alignedLocatorY, NativeStructureOwnershipFingerprint.locatorY(reloaded));
assertEquals(alignedLocatorY, ownership.locatorY());
assertEquals(1, reloaded.getPieces().size());
assertEquals(1, wrapped.getPieces().size());
}
@Test
public void sameKeyAndOriginStillRequiresMatchingContentIdentity() {
long seed = 18273L;
ChunkPos origin = new ChunkPos(2, 3);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart expected = monumentStart(structure, origin, seed);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
expected,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()),
expected.getBoundingBox());
assertFalse(NativeStructureOwnershipFingerprint.matches(ownership, moved));
}
@Test
public void persistedEnvelopeRepairsReloadedMonumentReferenceOutsideLiveBounds() {
long seed = 772931L;
ChunkPos origin = new ChunkPos(4, -7);
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
StructureStart generated = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(wrapped.getPieces()));
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
ChunkPos target = outsideLiveBounds(ownership, reloaded);
assertNotNull(target);
assertTrue(NativeStructureReferenceRepair.requiresReference(
target, "minecraft:monument", reloaded, ownership));
assertFalse(NativeStructureReferenceRepair.requiresReference(
target, "minecraft:monument", reloaded, null));
}
@Test
public void overlappingManualStartsSixteenChunksApartRemainIndependentlyReferenced() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos westOrigin = new ChunkPos(0, 0);
ChunkPos eastOrigin = new ChunkPos(16, 0);
StructureStart west = monumentStart(structure, westOrigin, 317L);
StructureStart east = monumentStart(structure, eastOrigin, 941L);
west.getPieces().getFirst().move(112, 0, 0);
east.getPieces().getFirst().move(-96, 0, 0);
NativeStructureOwnershipRecord westOwnership = ownership(westOrigin, west, structure);
NativeStructureOwnershipRecord eastOwnership = ownership(eastOrigin, east, structure);
ChunkPos shared = new ChunkPos(8, 0);
assertTrue(NativeStructureReferenceEnvelope.contentBounds(west).intersects(
NativeStructureReferenceEnvelope.contentBounds(east)));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", west, westOwnership));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", east, eastOwnership));
}
@Test
public void denseAdjacentManualStartsDoNotSuppressEachOther() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos firstOrigin = new ChunkPos(0, 0);
ChunkPos secondOrigin = new ChunkPos(1, 0);
StructureStart first = monumentStart(structure, firstOrigin, 17L);
StructureStart second = monumentStart(structure, secondOrigin, 23L);
NativeStructureOwnershipRecord firstOwnership = ownership(firstOrigin, first, structure);
NativeStructureOwnershipRecord secondOwnership = ownership(secondOrigin, second, structure);
ChunkPos shared = new ChunkPos(0, 0);
assertTrue(NativeStructureReferenceEnvelope.contentBounds(first).intersects(
NativeStructureReferenceEnvelope.contentBounds(second)));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", first, firstOwnership));
assertTrue(NativeStructureReferenceRepair.requiresReference(
shared, "minecraft:monument", second, secondOwnership));
}
@Test
public void customPoolElementIdentityDoesNotDependOnProcessLocalToString() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos origin = new ChunkPos(3, -2);
StructureStart first = poolStart(structure, origin, new IdentityStringPoolElement());
StructureStart reloaded = poolStart(structure, origin, new IdentityStringPoolElement());
assertEquals(
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", first),
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", reloaded));
}
@Test
public void namedPoolTemplateRemainsPartOfTheStableFingerprint() {
OceanMonumentStructure structure = new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
ChunkPos origin = new ChunkPos(3, -2);
StructurePoolElement firstElement = StructurePoolElement.single("test:first")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement secondElement = StructurePoolElement.single("test:second")
.apply(StructureTemplatePool.Projection.RIGID);
assertNotEquals(
NativeStructureOwnershipFingerprint.fingerprint(
"test:named_pool", poolStart(structure, origin, firstElement)),
NativeStructureOwnershipFingerprint.fingerprint(
"test:named_pool", poolStart(structure, origin, secondElement)));
}
private static StructureStart monumentStart(OceanMonumentStructure structure,
ChunkPos origin, long seed) {
WorldgenRandom random = new WorldgenRandom(
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
random,
origin.getMinBlockX() - 29,
origin.getMinBlockZ() - 29,
orientation
);
return new StructureStart(
structure,
origin,
0,
new PiecesContainer(List.of(building))
);
}
private static NativeStructureStartPlan plan(ChunkPos origin, int baseY) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
.setWeight(1);
IrisStructurePlacement placement = new IrisStructurePlacement()
.setPlacementId("monument-test")
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
return new NativeStructureStartPlan(
placement,
source,
origin.x(),
origin.z(),
baseY
);
}
private static NativeStructureOwnershipRecord ownership(
ChunkPos origin, StructureStart start, OceanMonumentStructure structure) {
return NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
start,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())
);
}
private static StructureStart poolStart(
OceanMonumentStructure structure, ChunkPos origin, StructurePoolElement element) {
BlockPos position = new BlockPos(
origin.getMiddleBlockX(), 64, origin.getMiddleBlockZ());
PoolElementStructurePiece piece = new PoolElementStructurePiece(
null, element, position, 0, Rotation.NONE,
new BoundingBox(position), LiquidSettings.APPLY_WATERLOGGING);
return new StructureStart(
structure, origin, 0, new PiecesContainer(List.of(piece)));
}
private static final class IdentityStringPoolElement extends StructurePoolElement {
private IdentityStringPoolElement() {
super(StructureTemplatePool.Projection.RIGID);
}
@Override
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
return new Vec3i(1, 1, 1);
}
@Override
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
StructureTemplateManager templateManager, BlockPos position,
Rotation rotation, RandomSource random) {
return List.of();
}
@Override
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
BlockPos position, Rotation rotation) {
return new BoundingBox(position);
}
@Override
public boolean place(StructureTemplateManager templateManager,
WorldGenLevel world, StructureManager structureManager,
ChunkGenerator chunkGenerator, BlockPos position, BlockPos pivot,
Rotation rotation, BoundingBox area, RandomSource random,
LiquidSettings liquidSettings, boolean keepJigsaws) {
return true;
}
@Override
public StructurePoolElementType<?> getType() {
return StructurePoolElementType.EMPTY;
}
@Override
public String toString() {
return "IdentityStringPoolElement@"
+ Integer.toHexString(System.identityHashCode(this));
}
}
private static ChunkPos outsideLiveBounds(NativeStructureOwnershipRecord ownership,
StructureStart start) {
for (int chunkX = ownership.referenceMinChunkX();
chunkX <= ownership.referenceMaxChunkX(); chunkX++) {
for (int chunkZ = ownership.referenceMinChunkZ();
chunkZ <= ownership.referenceMaxChunkZ(); chunkZ++) {
ChunkPos candidate = new ChunkPos(chunkX, chunkZ);
if (!start.getBoundingBox().intersects(
candidate.getMinBlockX(), candidate.getMinBlockZ(),
candidate.getMaxBlockX(), candidate.getMaxBlockZ())) {
return candidate;
}
}
}
return null;
}
}
@@ -0,0 +1,314 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.placement.StructurePlacementGrid;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructureOwnershipRecoveryTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void missingSidecarRecoversExactPolicyEnvelopeAndLocatorAfterReload() {
long seed = 5519231L;
ChunkPos origin = new ChunkPos(7, -5);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
expected, 0, 80, -64, 320);
int expectedLocatorY = NativeStructureOwnershipFingerprint.locatorY(expected);
NativeStructureStartPlan plan = plan(origin, "recovery-exact", 24);
StructureStart reloaded = new StructureStart(
structure,
origin,
expected.getReferences(),
OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(expected.getPieces()))
);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, reloaded,
plan, expected, false);
assertNotNull(recovered);
assertEquals(StructurePlacementGrid.placementIdentity(plan.placement()),
recovered.placementIdentity());
assertEquals(expectedLocatorY, recovered.locatorY());
assertNotEquals(NativeStructureOwnershipFingerprint.locatorY(reloaded),
recovered.locatorY());
assertEquals(IrisStructureTerrainMode.FORCE_CARVE,
recovered.restoredDecision().terrain().resolvedMode());
assertEquals(24, recovered.restoredDecision().terrain().getHorizontalPadding());
assertTrue(recovered.referenceMinChunkX()
< NativeStructureReferenceEnvelope.contentBounds(reloaded).minX() >> 4);
}
@Test
public void naturalSameKeyStartRemainsUnownedWhenOriginIsAmbiguous() {
long seed = 22817L;
ChunkPos origin = new ChunkPos(-3, 6);
OceanMonumentStructure structure = structure();
StructureStart natural = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(origin, "natural-ambiguous", 0);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, natural,
plan, natural, true);
assertNull(recovered);
}
@Test
public void changedPlacementIdentityOrGeometryCannotRecoverOwnership() {
long seed = 991723L;
ChunkPos origin = new ChunkPos(2, 4);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
StructureStart changedGeometry = monumentStart(structure, origin, seed);
for (StructurePiece piece : changedGeometry.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureStartPlan originalIdentity = plan(
origin, "original-identity", 0);
NativeStructureStartPlan changedIdentity = plan(
new ChunkPos(origin.x() + 1, origin.z()), "changed-identity", 0);
assertNotEquals(
StructurePlacementGrid.placementIdentity(originalIdentity.placement()),
StructurePlacementGrid.placementIdentity(changedIdentity.placement()));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
changedIdentity, expected, false));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, changedGeometry,
plan(origin, "geometry-check", 0), expected, false));
}
@Test
public void recoveredEnvelopeStillRequiresExactFingerprint() {
long seed = 81337L;
ChunkPos origin = new ChunkPos(-8, -9);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
plan(origin, "fingerprint-check", 16), expected, false);
assertNotNull(recovered);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(0, 0, 1);
}
assertFalse(NativeStructureOwnershipFingerprint.matches(recovered, moved));
}
@Test
public void staleVacuumEnvelopeRefreshesWithoutReplacingOwnershipIdentity() {
String structureKey = "minecraft:monument";
long seed = 648231L;
ChunkPos origin = new ChunkPos(5, -6);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-envelope-refresh", IrisStructureTerrainMode.VACUUM, 0);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(start);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, content);
NativeStructureOwnershipRecord refreshed =
NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, stale);
assertNotNull(refreshed);
assertNotEquals(stale, refreshed);
assertEquals(stale.schema(), refreshed.schema());
assertEquals(stale.ownershipKey(), refreshed.ownershipKey());
assertEquals(stale.placementIdentity(), refreshed.placementIdentity());
assertEquals(stale.baseY(), refreshed.baseY());
assertEquals(stale.locatorY(), refreshed.locatorY());
assertEquals(stale.contentFingerprint(), refreshed.contentFingerprint());
assertEquals(stale.decision(), refreshed.decision());
assertEquals(IrisStructureTerrainMode.VACUUM,
refreshed.restoredDecision().terrain().resolvedMode());
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
assertEquals(expected.minX() >> 4, refreshed.referenceMinChunkX());
assertEquals(expected.maxX() >> 4, refreshed.referenceMaxChunkX());
assertEquals(expected.minZ() >> 4, refreshed.referenceMinChunkZ());
assertEquals(expected.maxZ() >> 4, refreshed.referenceMaxChunkZ());
assertTrue(hasExpandedCoverage(stale, refreshed));
assertSame(refreshed, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, refreshed));
}
@Test
public void staleEnvelopeCannotRefreshAgainstDifferentContent() {
String structureKey = "minecraft:monument";
long seed = 412987L;
ChunkPos origin = new ChunkPos(-2, 7);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-content-check", IrisStructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, expected, plan,
NativeStructureReferenceEnvelope.contentBounds(expected));
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
assertNull(NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, moved, stale));
}
@Test
public void currentNonVacuumEnvelopeRemainsThePersistedAuthority() {
String structureKey = "minecraft:monument";
long seed = 927451L;
ChunkPos origin = new ChunkPos(3, 8);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(origin, "current-force-carve", 24);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
}
@Test
public void clippedVacuumEnvelopeRemainsStableAtTheReferenceLimit() {
String structureKey = "minecraft:monument";
long seed = 381729L;
ChunkPos origin = new ChunkPos(0, 0);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
BoundingBox initial = NativeStructureReferenceEnvelope.contentBounds(start);
int maximumReferenceBlockX = ((origin.x()
+ NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
int shiftX = maximumReferenceBlockX - initial.maxX();
for (StructurePiece piece : start.getPieces()) {
piece.move(shiftX, 0, 0);
}
NativeStructureStartPlan plan = plan(
origin, "clipped-vacuum", IrisStructureTerrainMode.VACUUM, 0);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
assertEquals(origin.x() + NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS,
ownership.referenceMaxChunkX());
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
}
private static OceanMonumentStructure structure() {
return new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
}
private static StructureStart monumentStart(OceanMonumentStructure structure,
ChunkPos origin, long seed) {
WorldgenRandom random = new WorldgenRandom(
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
OceanMonumentPieces.MonumentBuilding building =
new OceanMonumentPieces.MonumentBuilding(
random,
origin.getMinBlockX() - 29,
origin.getMinBlockZ() - 29,
orientation
);
return new StructureStart(
structure, origin, 0, new PiecesContainer(List.of(building)));
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
int horizontalPadding) {
return plan(origin, placementId,
IrisStructureTerrainMode.FORCE_CARVE, horizontalPadding);
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
IrisStructureTerrainMode terrainMode,
int horizontalPadding) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
.setWeight(1);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(terrainMode)
.setHorizontalPadding(horizontalPadding);
IrisStructurePlacement placement = new IrisStructurePlacement()
.setPlacementId(placementId)
.setTerrain(terrain)
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
return new NativeStructureStartPlan(
placement,
source,
origin.x(),
origin.z(),
NativeStructureReferenceEnvelope.contentBounds(
monumentStart(structure(), origin, 1L)).minY()
);
}
private static boolean hasExpandedCoverage(
NativeStructureOwnershipRecord stale,
NativeStructureOwnershipRecord refreshed) {
for (int chunkX = refreshed.referenceMinChunkX();
chunkX <= refreshed.referenceMaxChunkX(); chunkX++) {
for (int chunkZ = refreshed.referenceMinChunkZ();
chunkZ <= refreshed.referenceMaxChunkZ(); chunkZ++) {
if (!stale.covers(chunkX, chunkZ)) {
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,532 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.iris.structure.placement.IrisStructureYBand;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorEncaseTest {
private static final long TEST_SEED = 1234L;
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void encaseFillsAirAndLiquidWithoutOverwritingExistingTerrain() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 1, bounds.minY() - 1, bounds.minZ() - 1,
bounds.maxX() + 1, bounds.maxY() + 1, bounds.maxZ() + 1);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, bounds.minX(), bounds.minY(), bounds.minZ(), Blocks.DIRT.defaultBlockState());
put(blocks, bounds.maxX(), bounds.minY(), bounds.maxZ(), Blocks.WATER.defaultBlockState());
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(1)
.setCeilingPadding(1)
.setFloorPadding(1),
null);
assertEquals(Blocks.DIRT.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.maxX(), bounds.minY(), bounds.maxZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 1, bounds.minY() - 1, bounds.minZ() - 1));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.maxX() + 1, bounds.maxY() + 1, bounds.maxZ() + 1));
}
@Test
public void defaultEncasePaletteSplitsStoneAboveZeroAndDeepslateBelow() {
StructureStart start = start(TerrainAdjustment.BURY, -1);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.ENCASE),
null);
assertEquals(-1, bounds.minY());
assertEquals(Blocks.DEEPSLATE.defaultBlockState(),
state(blocks, bounds.minX(), -1, bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), 0, bounds.minZ()));
}
@Test
public void defaultEncaseMaterialFollowsVanillaDimensionTerrain() {
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.NETHER, 64));
assertEquals(Blocks.END_STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.END, 64));
assertEquals(Blocks.STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.OVERWORLD, 64));
}
@Test
public void sourceEncaseMaterialFollowsNearbyIrisTerrain() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(4, 64, 8);
put(blocks, fill.getX() + 3, fill.getY(), fill.getZ(), Blocks.TUFF.defaultBlockState());
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
}
@Test
public void sourceMaterialSnapshotIgnoresPreviouslyDecoratedAdjacentChunks() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(0, 64, 8);
put(blocks, -1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
WorldGenLevel world = world(blocks);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(
world, fill, sourceTerrain));
}
@Test
public void sourceMaterialSnapshotIsStableAcrossEarlierFillsInTheSameChunk() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(0, 64, 8);
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
WorldGenLevel world = world(blocks);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
put(blocks, 1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
assertEquals(Blocks.TUFF.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(
world, fill, sourceTerrain));
}
@Test
public void sourceMaterialRejectsStructureBlocksAndOresBeforeDimensionFallback() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockPos fill = new BlockPos(4, -20, 8);
put(blocks, 3, -20, 8, Blocks.OAK_PLANKS.defaultBlockState());
put(blocks, 5, -20, 8, Blocks.STONE_BRICKS.defaultBlockState());
put(blocks, 4, -20, 7, Blocks.IRON_ORE.defaultBlockState());
assertEquals(Blocks.DEEPSLATE.defaultBlockState(),
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
}
@Test
public void sourceMaterialSnapshotOnlyAllocatesActualFillLayers() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox area = new BoundingBox(0, -2032, 0, 15, 2030, 15);
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
NativeStructureTerrainIntegrator.captureSourceTerrain(
world(new HashMap<>()), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"test:bounded-snapshot", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
assertNotNull(sourceTerrain);
assertEquals(23 * 16 * 16, sourceTerrain.sampledCells());
}
@Test
public void customDimensionKeyUsesRegisteredVanillaDimensionType() {
ResourceKey<Level> customDimension = ResourceKey.create(
Registries.DIMENSION, Identifier.parse("iris:custom_nether"));
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(
customDimension, BuiltinDimensionTypes.NETHER, null, 64));
assertEquals(Blocks.END_STONE.defaultBlockState(),
NativeStructureTerrainIntegrator.defaultEncaseBlock(
customDimension, BuiltinDimensionTypes.END, null, 64));
}
@Test
public void configuredEncasePaletteReplacesTheDefaultMaterial() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
IrisMaterialPalette palette = new IrisMaterialPalette().qclear().qadd("minecraft:tuff");
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setEncasePalette(palette),
NativeStructurePostProcessorEncaseTest::tuffBlock);
assertEquals(Blocks.TUFF.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
}
@Test
public void omittedTerrainConfigurationRetainsSourceSemantics() {
for (TerrainAdjustment adjustment : TerrainAdjustment.values()) {
IrisStructureTerrain resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(adjustment, 64), null);
assertEquals(IrisStructureTerrainMode.SOURCE, resolved.resolvedMode());
}
}
@Test
public void sourceBuryUsesTheVanillaSixByTwelveInfluenceEnvelope() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
int groundY = bounds.minY();
BoundingBox area = new BoundingBox(
bounds.minX() - 7, groundY - 13, bounds.minZ(),
bounds.maxX(), groundY + 13, bounds.maxZ());
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:bury", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 5, groundY, bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 11, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 6, groundY, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 12, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 7, groundY, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), groundY + 13, bounds.minZ()));
}
@Test
public void sourceEncapsulateWrapsTheFullPieceBounds() {
StructureStart start = start(TerrainAdjustment.ENCAPSULATE, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 13, bounds.minY(), bounds.minZ(),
bounds.maxX(), bounds.maxY() + 13, bounds.maxZ());
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:encapsulate", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 11, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 11, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 12, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 12, bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX() - 13, bounds.minY(), bounds.minZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), bounds.maxY() + 13, bounds.minZ()));
}
@Test
public void sourceTerrainDensityOnlyUsesRigidPoolPieces() {
PoolElementStructurePiece terrainMatching = poolPiece(StructureTemplatePool.Projection.TERRAIN_MATCHING);
PoolElementStructurePiece rigid = poolPiece(StructureTemplatePool.Projection.RIGID);
assertFalse(NativeStructureTerrainIntegrator.isSourceRigidPiece(terrainMatching));
assertTrue(NativeStructureTerrainIntegrator.isSourceRigidPiece(rigid));
}
@Test
public void sourceJunctionTerrainUsesTheStrictLowerTwelveBlockEnvelope() {
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -1, 0));
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -11, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, 0, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -12, 0));
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(12, -1, 0));
}
@Test
public void explicitTerrainConfigurationWinsOverAutoEncase() {
IrisStructureTerrain configured = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
assertSame(configured, NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(TerrainAdjustment.BURY, 64), configured));
}
@Test
public void legacyTemplateAirIsClearedAfterEveryTerrainFillPath() {
IrisStructureTerrain source = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
IrisStructureTerrain preserve = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
IrisStructureTerrain encase = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE);
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), source));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BEARD_BOX, 64), source));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.NONE, 64), encase));
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.NONE, 64), source));
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), preserve));
}
@Test
public void encaseReservesTheNeighborChunkEnvelope() {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox sourceBounds = generated.getBoundingBox();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(4);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
terrain);
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
wrapped, wrapped.getStructure(), terrain);
assertEquals(sourceBounds, wrapped.getBoundingBox());
assertEquals(content.minX() - 4, referenceBounds.minX());
assertEquals(content.maxX() + 4, referenceBounds.maxX());
assertEquals(content.minZ() - 4, referenceBounds.minZ());
assertEquals(content.maxZ() + 4, referenceBounds.maxZ());
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
}
@Test
public void referenceEnvelopeRejectsChunkCoordinateOverflow() {
StructureStart generated = start(TerrainAdjustment.NONE, 64);
StructureStart extreme = new StructureStart(
generated.getStructure(), new ChunkPos(Integer.MAX_VALUE, 0), 0,
new PiecesContainer(generated.getPieces()));
assertThrows(IllegalStateException.class, () ->
NativeStructureReferenceEnvelope.referenceBounds(
extreme, extreme.getStructure(), new IrisStructureTerrain()));
}
@Test
public void explicitTerrainEnvelopeDoesNotInheritSourceAdjustment() {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(112);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated, generated.getStructure(), 0, terrain);
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
wrapped, wrapped.getStructure(), terrain);
assertEquals(content.minX() - 112, referenceBounds.minX());
assertEquals(content.maxX() + 112, referenceBounds.maxX());
}
@Test
public void yBandRelocatesTheStartMidpointDeterministically() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-120).setMax(-20);
StructureStart first = start(TerrainAdjustment.BURY, 64);
StructureStart second = start(TerrainAdjustment.BURY, 64);
NativeStructureVerticalPlacer.applyVerticalShift(
first, -64, -256, 320, true, false, band, (x, z) -> 40);
NativeStructureVerticalPlacer.applyVerticalShift(
second, -64, -256, 320, true, false, band, (x, z) -> 40);
BoundingBox bounds = first.getBoundingBox();
assertEquals(bounds.minY(), second.getBoundingBox().minY());
assertTrue(bounds.minY() >= -120);
assertTrue(bounds.maxY() <= -20);
int repeated = NativeStructureVerticalPlacer.applyVerticalShift(
first, -64, -256, 320, true, false, band, (x, z) -> 40);
assertEquals(0, repeated);
}
@Test
public void yBandClampsWhenTheBandCannotContainTheStructure() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-50).setMax(-45);
StructureStart start = start(TerrainAdjustment.BURY, 64);
NativeStructureVerticalPlacer.applyVerticalShift(
start, 0, -256, 320, true, false, band, (x, z) -> 40);
BoundingBox bounds = start.getBoundingBox();
int midpoint = bounds.minY() + (bounds.maxY() - bounds.minY()) / 2;
assertTrue(midpoint >= -50);
assertTrue(midpoint <= -45);
}
@Test
public void yBandReplacesBothTheBlindShiftAndBurial() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-64).setMax(-64);
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getBoundingBox();
int height = bounds.maxY() - bounds.minY();
NativeStructureVerticalPlacer.applyVerticalShift(
start, -200, -256, 320, true, false, band, (x, z) -> 40);
assertEquals(-64 - height / 2, start.getBoundingBox().minY());
}
@Test
public void preserveSourceYWinsOverTheConfiguredBand() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-120).setMax(-20);
StructureStart start = start(TerrainAdjustment.BURY, 64);
int minY = start.getBoundingBox().minY();
int offset = NativeStructureVerticalPlacer.applyVerticalShift(
start, 0, -256, 320, true, true, band, (x, z) -> 40);
assertEquals(0, offset);
assertEquals(minY, start.getBoundingBox().minY());
}
private static BlockState tuffBlock(IrisMaterialPalette palette, RNG rng, int x, int y, int z) {
assertNotNull(palette);
assertNotNull(rng);
return Blocks.TUFF.defaultBlockState();
}
private static StructureStart start(TerrainAdjustment adjustment, int minY) {
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(
HolderSet.empty(), Map.of(), GenerationStep.Decoration.STRONGHOLDS, adjustment));
StructurePiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
piece.move(0, minY - piece.getBoundingBox().minY(), 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static PoolElementStructurePiece poolPiece(StructureTemplatePool.Projection projection) {
BlockPos position = new BlockPos(0, 64, 0);
StructurePoolElement element = StructurePoolElement.single(
"minecraft:empty",
Holder.direct(new StructureProcessorList(List.of(
BlockIgnoreProcessor.STRUCTURE_AND_AIR))),
LiquidSettings.APPLY_WATERLOGGING
).apply(projection);
return new PoolElementStructurePiece(
null,
element,
position,
0,
Rotation.NONE,
new BoundingBox(position),
LiquidSettings.APPLY_WATERLOGGING);
}
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getBlockState")) {
BlockPos position = (BlockPos) arguments[0];
return state(blocks, position.getX(), position.getY(), position.getZ());
}
if (methodName.equals("setBlock")) {
BlockPos position = (BlockPos) arguments[0];
BlockState blockState = (BlockState) arguments[1];
put(blocks, position.getX(), position.getY(), position.getZ(), blockState);
return true;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "encase-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceSnapshot(
WorldGenLevel world, BoundingBox area) {
BitSet requiredLayers = new BitSet(area.getYSpan());
requiredLayers.set(0, area.getYSpan());
return NativeStructureTerrainIntegrator.SourceTerrainSnapshot.capture(
world, area, requiredLayers);
}
private static void put(Map<BlockPos, BlockState> blocks,
int x, int y, int z, BlockState state) {
blocks.put(new BlockPos(x, y, z), state);
}
private static BlockState state(Map<BlockPos, BlockState> blocks, int x, int y, int z) {
return blocks.getOrDefault(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState());
}
}
@@ -0,0 +1,202 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorMonumentTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void vanillaSeaLevelKeepsTheVanillaMonumentHeight() {
StructureStart start = monumentStart(1337L);
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:monument", 0, 63, -64, 320, false, false, null, (x, z) -> 0);
assertEquals(0, offset);
assertEquals(39, start.getBoundingBox().minY());
assertEquals(61, start.getBoundingBox().maxY());
}
@Test
public void shiftedSeaLevelMovesTheShellAndEveryRoomTogether() {
StructureStart start = monumentStart(1337L);
OceanMonumentPieces.MonumentBuilding building = monumentBuilding(start);
List<StructurePiece> children = NativeStructureVerticalPlacer.monumentChildPieces(building);
int[] childMinY = minimumYs(children);
BoundingBox cachedBounds = start.getBoundingBox();
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-13, offset);
assertSame(cachedBounds, start.getBoundingBox());
assertEquals(26, building.getBoundingBox().minY());
assertEquals(48, building.getBoundingBox().maxY());
assertEquals(26, start.getBoundingBox().minY());
assertEquals(48, start.getBoundingBox().maxY());
assertTrue(children.size() > 20);
for (int i = 0; i < children.size(); i++) {
assertEquals(childMinY[i] - 13, children.get(i).getBoundingBox().minY());
}
int repeatedOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(0, repeatedOffset);
}
@Test
public void configuredOffsetIsRelativeToTheActualSeaLevel() {
StructureStart start = monumentStart(1337L);
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:monument", 3, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-10, offset);
assertEquals(29, start.getBoundingBox().minY());
assertEquals(51, start.getBoundingBox().maxY());
}
@Test
public void referenceEnvelopeDoesNotAddMonumentPieces() {
StructureStart start = monumentStart(1337L);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
start,
start.getStructure(),
0,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(4));
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
wrapped, "minecraft:monument", 0, 63, -64, 320,
false, false, null, (x, z) -> 0);
assertEquals(0, offset);
assertEquals(1, wrapped.getPieces().size());
}
@Test
public void vanillaReloadRegenerationIsRealignedBeforePlacement() {
long seed = 1337L;
ChunkPos chunkPos = new ChunkPos(0, 0);
StructureStart initial = monumentStart(seed);
NativeStructureVerticalPlacer.applyVerticalPlacement(
initial, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
chunkPos, seed, new PiecesContainer(initial.getPieces()));
StructureStart reloaded = new StructureStart(
monumentStructure(), chunkPos, 0, regenerated);
assertEquals(39, reloaded.getBoundingBox().minY());
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
reloaded, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-13, offset);
assertEquals(26, reloaded.getBoundingBox().minY());
assertEquals(48, reloaded.getBoundingBox().maxY());
}
@Test
public void neighboringChunkPlacementRealignsReloadedMonumentOnce() throws Exception {
long seed = 1337L;
ChunkPos chunkPos = new ChunkPos(0, 0);
StructureStart initial = monumentStart(seed);
NativeStructureVerticalPlacer.applyVerticalPlacement(
initial, "minecraft:monument", 0, 50, -256, 512,
false, false, null, (x, z) -> 0);
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
chunkPos, seed, new PiecesContainer(initial.getPieces()));
StructureStart reloaded = new StructureStart(
monumentStructure(), chunkPos, 0, regenerated);
ExecutorService executor = Executors.newFixedThreadPool(8);
CountDownLatch release = new CountDownLatch(1);
List<Future<?>> futures = new ArrayList<>();
try {
for (int task = 0; task < 16; task++) {
futures.add(executor.submit(() -> {
release.await();
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
reloaded, "minecraft:monument", 0, 50, -256, 512);
return null;
}));
}
release.countDown();
for (Future<?> future : futures) {
future.get();
}
} finally {
executor.shutdownNow();
}
assertEquals(26, reloaded.getBoundingBox().minY());
assertEquals(48, reloaded.getBoundingBox().maxY());
}
@Test
public void impossibleSeaLevelAlignmentFailsInsteadOfClippingTheMonument() {
StructureStart start = monumentStart(1337L);
try {
NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:monument", 0, -50, -64, 320, false, false, null, (x, z) -> 0);
} catch (IllegalStateException error) {
assertTrue(error.getMessage().contains("cannot align"));
return;
}
throw new AssertionError("Expected an out-of-bounds monument alignment to fail");
}
private static StructureStart monumentStart(long seed) {
ChunkPos chunkPos = new ChunkPos(0, 0);
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
RandomSource.create(seed), -29, -29, Direction.NORTH);
return new StructureStart(
monumentStructure(), chunkPos, 0, new PiecesContainer(List.of(building)));
}
private static OceanMonumentStructure monumentStructure() {
return new OceanMonumentStructure(new Structure.StructureSettings(HolderSet.empty()));
}
private static OceanMonumentPieces.MonumentBuilding monumentBuilding(StructureStart start) {
return (OceanMonumentPieces.MonumentBuilding) start.getPieces().get(0);
}
private static int[] minimumYs(List<StructurePiece> pieces) {
int[] ys = new int[pieces.size()];
for (int i = 0; i < pieces.size(); i++) {
ys[i] = pieces.get(i).getBoundingBox().minY();
}
return ys;
}
}
@@ -0,0 +1,198 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.structures.JungleTemplePiece;
import net.minecraft.world.level.levelgen.structure.structures.JungleTempleStructure;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorScatteredFeatureTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void desertPyramidUsesTheLowestFootprintSurfaceAndLocksVanillaReanchor() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(11L), 0, 0);
StructureStart start = desertStart(piece);
BoundingBox cachedBounds = start.getBoundingBox();
BoundingBox footprint = piece.getBoundingBox();
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null,
(x, z) -> x == footprint.maxX() && z == footprint.maxZ() ? 64 : 92);
assertEquals(1, offset);
assertSame(cachedBounds, start.getBoundingBox());
assertEquals(65, piece.getBoundingBox().minY());
assertEquals(65, start.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment(-2));
assertEquals(65, piece.getBoundingBox().minY());
}
@Test
public void jungleTempleUsesAverageFootprintSurfaceAndHonorsConfiguredShift() {
TestJungleTemplePiece piece = new TestJungleTemplePiece(RandomSource.create(19L), 32, -16);
StructureStart start = jungleStart(piece);
BoundingBox footprint = piece.getBoundingBox();
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:jungle_pyramid", 4, 63, -64, 320, false, false, null,
(x, z) -> x == footprint.minX() && z == footprint.minZ() ? 88 : 70);
assertEquals(11, offset);
assertEquals(75, piece.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment());
assertEquals(75, piece.getBoundingBox().minY());
}
@Test
public void repeatedAlignmentIsIdempotent() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(23L), 0, 0);
StructureStart start = desertStart(piece);
int initialOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 80);
int repeatedOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 80);
assertEquals(17, initialOffset);
assertEquals(0, repeatedOffset);
assertEquals(81, piece.getBoundingBox().minY());
}
@Test
public void scatteredAlignmentStaysInsideWorldBounds() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(29L), 0, 0);
StructureStart start = desertStart(piece);
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 318);
assertEquals(241, offset);
assertEquals(305, piece.getBoundingBox().minY());
assertEquals(319, start.getBoundingBox().maxY());
}
@Test
public void scatteredStructuresDoNotOptIntoFullFootprintSurfaceFitting() {
TestDesertPyramidPiece desert = new TestDesertPyramidPiece(RandomSource.create(31L), 0, 0);
TestJungleTemplePiece jungle = new TestJungleTemplePiece(RandomSource.create(37L), 0, 0);
SwampHutPiece swamp = new SwampHutPiece(RandomSource.create(41L), 0, 0);
StructureStart desertStart = desertStart(desert);
StructureStart jungleStart = jungleStart(jungle);
StructureStart swampStart = swampStart(swamp);
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(desertStart));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(jungleStart));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(swampStart));
AtomicInteger terrainQueries = new AtomicInteger();
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
swampStart, "minecraft:swamp_hut", 0, 63, -64, 320, false, false, null,
(x, z) -> terrainQueries.incrementAndGet());
assertEquals(0, offset);
assertEquals(0, terrainQueries.get());
assertEquals(64, swamp.getBoundingBox().minY());
}
@Test
public void scatteredHeightFieldMatchesTheRuntimeContract() {
Field field = NativeStructureReflection.resolveScatteredHeightPositionField();
assertEquals(ScatteredFeaturePiece.class, field.getDeclaringClass());
assertEquals(int.class, field.getType());
assertTrue(Modifier.isProtected(field.getModifiers()));
assertFalse(Modifier.isFinal(field.getModifiers()));
assertFalse(Modifier.isStatic(field.getModifiers()));
assertTrue(field.trySetAccessible());
}
@Test
public void datapackOverridesOfSpecialVanillaIdsUseGenericPlacement() {
StructureStart pyramidOverride = swampStart(
new SwampHutPiece(RandomSource.create(43L), 0, 0));
int pyramidMinY = pyramidOverride.getBoundingBox().minY();
int pyramidOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
pyramidOverride, "minecraft:desert_pyramid", 5,
63, -64, 320, false, false, null, (x, z) -> 200);
assertEquals(5, pyramidOffset);
assertEquals(pyramidMinY + 5, pyramidOverride.getBoundingBox().minY());
StructureStart monumentOverride = swampStart(
new SwampHutPiece(RandomSource.create(47L), 0, 0));
int monumentMinY = monumentOverride.getBoundingBox().minY();
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
monumentOverride, "minecraft:monument", 0, 40, -64, 320);
assertEquals(monumentMinY, monumentOverride.getBoundingBox().minY());
}
private static StructureStart desertStart(StructurePiece piece) {
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart jungleStart(StructurePiece piece) {
Structure structure = new JungleTempleStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart swampStart(StructurePiece piece) {
Structure structure = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart start(Structure structure, StructurePiece piece) {
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static final class TestDesertPyramidPiece extends DesertPyramidPiece {
private TestDesertPyramidPiece(RandomSource random, int west, int north) {
super(random, west, north);
}
private boolean attemptVanillaAlignment(int offset) {
return updateHeightPositionToLowestGroundHeight((LevelAccessor) null, offset);
}
}
private static final class TestJungleTemplePiece extends JungleTemplePiece {
private TestJungleTemplePiece(RandomSource random, int west, int north) {
super(random, west, north);
}
private boolean attemptVanillaAlignment() {
return updateAverageGroundHeight((LevelAccessor) null, null, 0);
}
}
}
@@ -0,0 +1,354 @@
package art.arcane.iris.nativegen;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorVegetationTest {
private static final long TEST_SEED = 1234L;
private static final int MIN_Y = -64;
private static final int MAX_Y = 319;
private static final int GROUND_Y = 64;
private static final BoundingBox AREA = new BoundingBox(0, MIN_Y, 0, 15, MAX_Y, 15);
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void canopyAboveTheStructureRoofSurvivesVegetationClearing() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
for (int y = bounds.maxY() - 1; y <= GROUND_Y + 40; y++) {
fillLayer(chunk, y, leaves());
}
write(chunk, 4, bounds.minY() - 1, 4, log());
clearVegetation(chunk, start);
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
assertEquals(leaves(), chunk.getBlockState(new BlockPos(x, GROUND_Y + 40, z)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(x, bounds.maxY() + 1, z)));
assertEquals(air(), chunk.getBlockState(new BlockPos(x, bounds.maxY(), z)));
assertEquals(air(), chunk.getBlockState(new BlockPos(x, bounds.maxY() - 1, z)));
}
}
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 1, 4)));
}
@Test
public void buriedStructuresPreserveTheForestAboveThem() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, -20);
write(chunk, 8, GROUND_Y + 1, 8, log());
write(chunk, 8, GROUND_Y + 2, 8, log());
write(chunk, 8, GROUND_Y + 3, 8, log());
write(chunk, 8, GROUND_Y + 4, 8, leaves());
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 1, 8)));
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 2, 8)));
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 3, 8)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 4, 8)));
}
@Test
public void columnsOutsideThePieceFootprintKeepEveryTreeBlock() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(8, 8, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
int inside = bounds.minY() + 4;
write(chunk, 0, inside, 0, log());
write(chunk, 0, GROUND_Y + 40, 0, leaves());
write(chunk, 12, inside, 12, log());
write(chunk, 12, GROUND_Y + 40, 12, leaves());
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(0, inside, 0)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(0, GROUND_Y + 40, 0)));
assertEquals(air(), chunk.getBlockState(new BlockPos(12, inside, 12)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(12, GROUND_Y + 40, 12)));
}
@Test
public void clearingStopsAtThePieceEnvelopeBoundaries() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
write(chunk, 4, bounds.maxY() + 1, 4, log());
write(chunk, 4, bounds.maxY(), 4, log());
write(chunk, 4, bounds.minY() - 1, 4, log());
write(chunk, 4, bounds.minY() - 2, 4, log());
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(4, bounds.maxY() + 1, 4)));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.maxY(), 4)));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 1, 4)));
assertEquals(log(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 2, 4)));
}
@Test
public void overlappingPieceEnvelopesMergeIntoOneClearedRange() {
ProtoChunk chunk = chunk();
StructureStart low = desertStart(0, 0, GROUND_Y);
StructureStart high = desertStart(0, 0, GROUND_Y + 40);
int between = GROUND_Y + 20;
write(chunk, 4, between, 4, log());
write(chunk, 4, high.getBoundingBox().maxY() + 1, 4, log());
write(chunk, 4, low.getBoundingBox().minY() - 2, 4, log());
NativeStructureVegetationClearer.clearIntersectingVegetation(
world(chunk), chunk, AREA, List.of(low, high));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, between, 4)));
assertEquals(log(), chunk.getBlockState(
new BlockPos(4, high.getBoundingBox().maxY() + 1, 4)));
assertEquals(log(), chunk.getBlockState(
new BlockPos(4, low.getBoundingBox().minY() - 2, 4)));
}
@Test
public void validStartsReachIntersectionProcessing() {
assertTrue(NativeStructureVegetationClearer.shouldProcessStart(
desertStart(0, 0, GROUND_Y + 15)));
assertFalse(NativeStructureVegetationClearer.shouldProcessStart(
StructureStart.INVALID_START));
}
@Test
public void allUndergroundGenerationStepsShareOneClassification() {
assertTrue(NativeStructureVegetationClearer.isUndergroundStep(
GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
assertTrue(NativeStructureVegetationClearer.isUndergroundStep(
GenerationStep.Decoration.UNDERGROUND_DECORATION));
assertTrue(NativeStructureVegetationClearer.isUndergroundStep(
GenerationStep.Decoration.STRONGHOLDS));
assertFalse(NativeStructureVegetationClearer.isUndergroundStep(
GenerationStep.Decoration.SURFACE_STRUCTURES));
}
@Test
public void undergroundStructuresUseTheLowestTerrainColumn() {
BoundingBox bounds = new BoundingBox(0, 60, 0, 1, 80, 0);
int offset = NativeStructureVerticalPlacer.resolveBuriedOffset(
bounds, 0, -64, 320, (x, z) -> x == 1 ? 76 : 100);
assertEquals(-5, offset);
}
@Test
public void undergroundBurialClampsToTheWorldFloorInsteadOfFailing() {
BoundingBox bounds = new BoundingBox(0, 60, 0, 1, 80, 0);
assertEquals(-2, NativeStructureVerticalPlacer.resolveBuriedOffset(
bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100));
}
@Test
public void loweringSurfaceRemovesCactusStacksAndTheirFlowers() {
ProtoChunk chunk = chunk();
for (int y = 80; y <= 102; y++) {
write(chunk, 4, y, 4, Blocks.SAND.defaultBlockState());
}
for (int y = 103; y <= 105; y++) {
write(chunk, 4, y, 4, Blocks.CACTUS.defaultBlockState());
}
write(chunk, 4, 106, 4, Blocks.CACTUS_FLOWER.defaultBlockState());
NativeStructureSurfaceFitter.applySurfaceColumn(
world(chunk), new BlockPos.MutableBlockPos(), 4, 4, 102, 93, MIN_Y, MAX_Y);
assertEquals(Blocks.SAND.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 93, 4)));
for (int y = 94; y <= 106; y++) {
assertTrue("Unsupported decoration at " + y, chunk.getBlockState(new BlockPos(4, y, 4)).isAir());
}
}
@Test
public void loweringSurfacePreservesCactusOnUnchangedColumns() {
ProtoChunk chunk = chunk();
for (int x : List.of(4, 5)) {
for (int y = 60; y <= 64; y++) {
write(chunk, x, y, 4, Blocks.SAND.defaultBlockState());
}
write(chunk, x, 65, 4, Blocks.CACTUS.defaultBlockState());
write(chunk, x, 66, 4, Blocks.CACTUS_FLOWER.defaultBlockState());
}
WorldGenLevel world = world(chunk);
NativeStructureSurfaceFitter.applySurfaceColumn(
world, new BlockPos.MutableBlockPos(), 4, 4, 64, 60, MIN_Y, MAX_Y);
NativeStructureSurfaceFitter.applySurfaceColumn(
world, new BlockPos.MutableBlockPos(), 5, 4, 64, 64, MIN_Y, MAX_Y);
assertTrue(chunk.getBlockState(new BlockPos(4, 65, 4)).isAir());
assertTrue(chunk.getBlockState(new BlockPos(4, 66, 4)).isAir());
assertEquals(Blocks.SAND.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 64, 4)));
assertEquals(Blocks.CACTUS.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 65, 4)));
assertEquals(Blocks.CACTUS_FLOWER.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 66, 4)));
}
@Test
public void loweringSurfaceKeepsTreeAndSolidObjectBoundaries() {
for (BlockState boundary : List.of(Blocks.OAK_LOG.defaultBlockState(), Blocks.OAK_PLANKS.defaultBlockState())) {
ProtoChunk chunk = chunk();
write(chunk, 4, 63, 4, Blocks.SAND.defaultBlockState());
write(chunk, 4, 64, 4, Blocks.SAND.defaultBlockState());
write(chunk, 4, 65, 4, boundary);
write(chunk, 4, 66, 4, Blocks.CACTUS.defaultBlockState());
write(chunk, 4, 67, 4, Blocks.CACTUS_FLOWER.defaultBlockState());
NativeStructureSurfaceFitter.applySurfaceColumn(
world(chunk), new BlockPos.MutableBlockPos(), 4, 4, 64, 60, MIN_Y, MAX_Y);
assertEquals(boundary, chunk.getBlockState(new BlockPos(4, 65, 4)));
assertEquals(Blocks.CACTUS.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 66, 4)));
assertEquals(Blocks.CACTUS_FLOWER.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 67, 4)));
}
}
private static void clearVegetation(ProtoChunk chunk, StructureStart start) {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world(chunk), chunk, AREA, List.of(start));
}
private static StructureStart desertStart(int originX, int originZ, int topY) {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.NONE));
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), originX, originZ);
piece.move(0, topY - piece.getBoundingBox().maxY(), 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static ProtoChunk chunk() {
return new ProtoChunk(
new ChunkPos(0, 0), UpgradeData.EMPTY,
LevelHeightAccessor.create(MIN_Y, MAX_Y - MIN_Y + 1), containerFactory(), null);
}
private static void fillLayer(ProtoChunk chunk, int y, BlockState state) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
write(chunk, x, y, z, state);
}
}
}
private static void write(ProtoChunk chunk, int x, int y, int z, BlockState state) {
LevelChunkSection section = chunk.getSection(chunk.getSectionIndex(y));
section.setBlockState(x, y & 15, z, state, false);
}
private static BlockState log() {
return Blocks.OAK_LOG.defaultBlockState();
}
private static BlockState leaves() {
return Blocks.OAK_LEAVES.defaultBlockState();
}
private static BlockState air() {
return Blocks.AIR.defaultBlockState();
}
private static WorldGenLevel world(ChunkAccess chunk) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getBlockState")) {
return chunk.getBlockState((BlockPos) arguments[0]);
}
if (methodName.equals("setBlock")) {
return chunk.setBlockState(
((BlockPos) arguments[0]).immutable(),
(BlockState) arguments[1], (int) arguments[2]) != null;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("holderLookup")) {
return BuiltInRegistries.BLOCK;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "vegetation-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static PalettedContainerFactory containerFactory() {
Strategy<BlockState> blockStates = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
BlockState air = Blocks.AIR.defaultBlockState();
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
Holder<Biome> defaultBiome = Holder.direct((Biome) null);
biomeIds.add(defaultBiome);
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(
name -> defaultBiome, holder -> "iris-test-biome");
return new PalettedContainerFactory(
blockStates,
air,
PalettedContainer.codecRW(BlockState.CODEC, blockStates, air),
biomes,
defaultBiome,
PalettedContainer.codecRO(biomeCodec, biomes, defaultBiome),
PalettedContainer.codecRW(biomeCodec, biomes, defaultBiome));
}
}
@@ -0,0 +1,236 @@
package art.arcane.iris.nativegen;
import com.mojang.datafixers.util.Either;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.tags.TagKey;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlackstoneReplaceProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockAgeProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.CappedProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.GravityProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.JigsawReplacementProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LavaSubmergedBlockProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.NopProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.ProtectedBlockProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructureTemplateOccupancyTest {
private static final BoundingBox PROCESSING_AREA = new BoundingBox(0, 56, 0, 15, 80, 0);
private static final int TEMPLATE_WIDTH = 32;
private static final int TERRAIN_HEIGHT = 64;
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void straddlingGravityPieceHeightReadsHonorTheVanillaClipContract() throws Exception {
ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece(
List.of(new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0))), recorder);
// 26.2 vanilla clips processor evaluation to the placement bounding box; 26.1.2 vanilla
// runs processors across the whole piece. Iris mirrors the pinned version's semantics.
assertEquals(expectedClippedColumns(), recorder.heightColumns);
}
@Test
public void cappedStraddlingPieceRunsProcessorsAcrossTheWholePiece() throws Exception {
ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece(List.of(
new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(0)))), recorder);
assertEquals(columns(0, TEMPLATE_WIDTH - 1), recorder.heightColumns);
}
@Test
public void cappedProcessorIsTheOnlyProcessorThatDisablesTheProcessingAreaClip() throws Exception {
Method contract = clipContractMethod();
Assume.assumeTrue("evaluatesEntirePieceState only exists on 26.2+", contract != null);
for (StructureProcessor processor : List.of(
BlockIgnoreProcessor.STRUCTURE_BLOCK,
JigsawReplacementProcessor.INSTANCE,
LavaSubmergedBlockProcessor.INSTANCE,
NopProcessor.INSTANCE,
BlackstoneReplaceProcessor.INSTANCE,
new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new RuleProcessor(List.of()),
new BlockAgeProcessor(0.5F),
protectedBlockProcessor())) {
assertFalse(processor.getClass().getName(),
(Boolean) contract.invoke(processor));
}
assertTrue((Boolean) contract.invoke(
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(4))));
}
@Test
public void placementSettingsCarryTheProcessingAreaAsTheirClipBox() throws Exception {
PoolElementStructurePiece piece = straddlingPiece(List.of());
StructurePlaceSettings settings = NativeStructureReflection.resolvePlacementSettings(
(SinglePoolElement) piece.getElement(), piece, PROCESSING_AREA);
assertEquals(PROCESSING_AREA, settings.getBoundingBox());
}
private static void resolve(PoolElementStructurePiece piece, ColumnRecorder recorder) {
NativeStructureTemplateOccupancy.resolve(
world(recorder), piece, piece.getPosition(), PROCESSING_AREA,
NativeStructureTemplateOccupancyTest::forbiddenTemplateManager,
PROCESSING_AREA::isInside, amount -> {
});
}
private static PoolElementStructurePiece straddlingPiece(
List<StructureProcessor> processors) throws Exception {
BoundingBox bounds = new BoundingBox(0, 70, 0, TEMPLATE_WIDTH - 1, 76, 0);
return new PoolElementStructurePiece(
null, new InlineSinglePoolElement(template(), processors),
new BlockPos(bounds.minX(), bounds.minY(), bounds.minZ()),
1, Rotation.NONE, bounds, LiquidSettings.APPLY_WATERLOGGING);
}
private static Set<Integer> columns(int minimumX, int maximumX) {
Set<Integer> columns = new TreeSet<>();
for (int x = minimumX; x <= maximumX; x++) {
columns.add(x);
}
return columns;
}
private static Method clipContractMethod() {
try {
return StructureProcessor.class.getMethod("evaluatesEntirePieceState");
} catch (NoSuchMethodException absent) {
return null;
}
}
private static Set<Integer> expectedClippedColumns() {
return clipContractMethod() != null
? columns(PROCESSING_AREA.minX(), PROCESSING_AREA.maxX())
: columns(0, TEMPLATE_WIDTH - 1);
}
private static StructureProcessor protectedBlockProcessor() throws Exception {
// Constructed reflectively: the constructor takes HolderSet on 26.2 and TagKey on 26.1.2.
Constructor<?> constructor = ProtectedBlockProcessor.class.getConstructors()[0];
Class<?> parameter = constructor.getParameterTypes()[0];
Object argument = parameter == HolderSet.class
? HolderSet.empty()
: TagKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath("minecraft", "air"));
return (StructureProcessor) constructor.newInstance(argument);
}
private static StructureTemplateManager forbiddenTemplateManager() {
throw new AssertionError("Inline templates must not resolve a template manager");
}
private static StructureTemplate template() throws Exception {
List<StructureTemplate.StructureBlockInfo> blocks = new ArrayList<>();
for (int x = 0; x < TEMPLATE_WIDTH; x++) {
blocks.add(new StructureTemplate.StructureBlockInfo(
new BlockPos(x, 0, 0), Blocks.COBBLESTONE.defaultBlockState(), null));
}
Constructor<StructureTemplate.Palette> constructor =
StructureTemplate.Palette.class.getDeclaredConstructor(List.class);
assertTrue(constructor.trySetAccessible());
StructureTemplate template = new StructureTemplate();
template.palettes.add(constructor.newInstance(blocks));
return template;
}
private static WorldGenLevel world(ColumnRecorder recorder) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getHeight") && arguments.length == 3) {
recorder.heightColumns.add((Integer) arguments[1]);
return TERRAIN_HEIGHT;
}
if (methodName.equals("getBlockState")) {
recorder.stateColumns.add(((BlockPos) arguments[0]).getX());
return Blocks.STONE.defaultBlockState();
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("holderLookup")) {
return BuiltInRegistries.BLOCK;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "occupancy-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(),
new Class<?>[]{WorldGenLevel.class}, handler);
}
private static final class ColumnRecorder {
private final Set<Integer> heightColumns = new TreeSet<>();
private final Set<Integer> stateColumns = new TreeSet<>();
}
private static final class InlineSinglePoolElement extends SinglePoolElement {
private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors) {
super(Either.right(template),
Holder.direct(new StructureProcessorList(processors)),
StructureTemplatePool.Projection.RIGID,
Optional.<LiquidSettings>empty());
}
}
}
@@ -0,0 +1,561 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureVolume;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Proxy;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.IntConsumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeStructureVolumeIndexTest {
private static final String STRUCTURE_KEY = "minecraft:swamp_hut";
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void assembledPiecesBecomeWorldSpacePieceVolumes() {
StructureStart start = swampHut(0, 0, 4, 6);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
KList<NativeStructureVolume> volumes = NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, start);
assertEquals(1, volumes.size());
NativeStructureVolume volume = volumes.getFirst();
assertEquals(STRUCTURE_KEY, volume.structure());
assertEquals(bounds.minX(), volume.minX());
assertEquals(bounds.minY(), volume.minY());
assertEquals(bounds.minZ(), volume.minZ());
assertEquals(bounds.maxX(), volume.maxX());
assertEquals(bounds.maxY(), volume.maxY());
assertEquals(bounds.maxZ(), volume.maxZ());
}
@Test
public void invalidStartsContributeNoVolumes() {
assertNull(NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START));
assertNull(NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, null));
}
@Test
public void coldAndCachedQueriesResolveIdenticalVolumes() {
CountingResolver resolver = new CountingResolver(0, 0);
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
KList<NativeStructureVolume> cold = index.resolve(null, 0, 0, 15, 15);
int coldResolutions = resolver.resolutions();
KList<NativeStructureVolume> cached = index.resolve(null, 0, 0, 15, 15);
assertTrue(coldResolutions > 0);
assertFalse(cold.isEmpty());
assertEquals(cold, cached);
assertEquals(coldResolutions, resolver.resolutions());
}
@Test
public void runtimeBucketsStayIsolatedAndRetirementEvictsOnlyTheirEntries() {
AtomicInteger runtimeId = new AtomicInteger(1);
Engine engine = runtimeEngine(runtimeId);
CountingResolver resolver = new CountingResolver(0, 0);
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(289, resolver.resolutions());
index.resolve(engine, 0, 0, 15, 15);
assertEquals(289, resolver.resolutions());
runtimeId.set(2);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(578, resolver.resolutions());
index.resolve(engine, 0, 0, 15, 15);
assertEquals(578, resolver.resolutions());
index.evictRuntime(1);
runtimeId.set(1);
index.resolve(engine, 0, 0, 15, 15);
assertEquals(867, resolver.resolutions());
}
@Test
public void pinnedRuntimeReusesCachedOriginsWithoutOpeningCoordinateScopes() throws Exception {
ScopedEngine scoped = scopedEngine(true, 1);
CountingResolver resolver = new CountingResolver(0, 0);
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
KList<NativeStructureVolume> first = index.resolve(scoped.engine(), 0, 0, 15, 15);
assertEquals(289, scoped.scopes().get());
index.resolve(scoped.engine(), 16, 0, 31, 15);
assertEquals(306, resolver.resolutions());
assertEquals(306, scoped.scopes().get());
assertEquals(first, index.resolve(scoped.engine(), 0, 0, 15, 15));
assertEquals(306, scoped.scopes().get());
}
@Test
public void absentOrMismatchedRuntimeOwnershipKeepsCoordinateRouting() throws Exception {
ScopedEngine detached = scopedEngine(true, 1);
when(detached.engine().getGenerationHistoryRuntimeRouter()).thenReturn(Optional.empty());
for (ScopedEngine scoped : List.of(scopedEngine(false, 1), scopedEngine(true, 0),
scopedEngine(true, 2), detached)) {
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(new CountingResolver(0, 0));
index.resolve(scoped.engine(), 0, 0, 15, 15);
index.resolve(scoped.engine(), 16, 0, 31, 15);
assertEquals(578, scoped.scopes().get());
}
}
@Test
public void retiredPinnedOriginsRebuildThroughCoordinateScopes() throws Exception {
ScopedEngine scoped = scopedEngine(true, 1);
CountingResolver resolver = new CountingResolver(0, 0);
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
KList<NativeStructureVolume> first = index.resolve(scoped.engine(), 0, 0, 15, 15);
index.evictRuntime(1);
assertEquals(first, index.resolve(scoped.engine(), 0, 0, 15, 15));
assertEquals(578, resolver.resolutions());
assertEquals(578, scoped.scopes().get());
}
@Test
public void invalidatedIndexReceivesRuntimeRetirementAndDetachesOnUninstall() throws Exception {
IrisEngine engine = mock(IrisEngine.class);
Set<IntConsumer> listeners = new CopyOnWriteArraySet<>();
doAnswer(invocation -> {
listeners.add(invocation.getArgument(0));
return null;
}).when(engine).addGenerationRuntimeRetirementListener(any());
doAnswer(invocation -> {
listeners.remove(invocation.getArgument(0));
return null;
}).when(engine).removeGenerationRuntimeRetirementListener(any());
NativeStructureVolumeIndex.Context context = new NativeStructureVolumeIndex.Context(
null, null, null, null, () -> null, () -> null, () -> null);
NativeStructureVolumeIndex.install(engine, context);
try {
assertEquals(1, listeners.size());
IntConsumer original = listeners.iterator().next();
NativeStructureVolumeIndex.invalidate(engine);
assertEquals(1, listeners.size());
assertFalse(listeners.contains(original));
NativeStructureVolumeIndex.volumes(engine, 0, 0, 15, 15);
NativeStructureVolumeIndex current = installedIndexes().get(engine);
assertEquals(289, cache(current, "originCache").size());
assertEquals(1, cache(current, "queryCache").size());
for (IntConsumer listener : listeners) {
listener.accept(0);
}
assertTrue(cache(current, "originCache").isEmpty());
assertTrue(cache(current, "queryCache").isEmpty());
} finally {
NativeStructureVolumeIndex.uninstall(engine);
}
assertTrue(listeners.isEmpty());
}
@Test
public void retiringOriginBuildCannotRepublishWhileQueryEvictionWaits() throws Exception {
BlockingResolver resolver = new BlockingResolver();
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
ExecutorService executor = Executors.newFixedThreadPool(2);
Map<?, ?> origins = cache(index, "originCache");
Map<?, ?> queries = cache(index, "queryCache");
try {
Future<KList<NativeStructureVolume>> builder = executor.submit(() -> index.originVolumes(null, 0, 0));
assertTrue(resolver.awaitFirstEntry());
index.originVolumes(null, 1, 0);
assertEquals(1, origins.size());
Future<?> retirement;
synchronized (queries) {
retirement = executor.submit(() -> index.evictRuntime(0));
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
boolean cleared = false;
while (System.nanoTime() < deadline) {
synchronized (origins) {
cleared = origins.isEmpty();
}
if (cleared) {
break;
}
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10L));
}
assertTrue("Origin eviction did not complete before the query cache lock", cleared);
resolver.release();
assertTrue(builder.get(5, TimeUnit.SECONDS).isEmpty());
synchronized (origins) {
assertTrue("The retired in-flight origin was cached again", origins.isEmpty());
}
}
retirement.get(5, TimeUnit.SECONDS);
index.originVolumes(null, 0, 0);
assertEquals(3, resolver.resolutions());
} finally {
resolver.release();
executor.shutdownNow();
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
}
}
@Test
public void independentIndexesResolveIdenticalVolumesFromTheSameSeed() {
NativeStructureVolumeIndex first = NativeStructureVolumeIndex.forTesting(new CountingResolver(0, 0));
NativeStructureVolumeIndex second = NativeStructureVolumeIndex.forTesting(new CountingResolver(0, 0));
assertEquals(first.resolve(null, 0, 0, 15, 15), second.resolve(null, 0, 0, 15, 15));
assertEquals(first.resolve(null, -32, -32, 47, 47), second.resolve(null, -32, -32, 47, 47));
}
@Test
public void volumesOutsideTheQueryRectAreExcluded() {
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(new CountingResolver(0, 0));
BoundingBox bounds = swampHut(0, 0, 4, 6).getPieces().getFirst().getBoundingBox();
KList<NativeStructureVolume> hit = index.resolve(null, bounds.minX(), bounds.minZ(), bounds.maxX(), bounds.maxZ());
KList<NativeStructureVolume> miss = index.resolve(null,
bounds.maxX() + 1, bounds.minZ(), bounds.maxX() + 8, bounds.maxZ());
assertEquals(1, hit.size());
assertTrue(miss.isEmpty());
}
@Test
public void startsWithinTheOriginReachContributeAndBeyondItDoNot() {
int reach = NativeStructureVolumeIndex.originReachChunks();
NativeStructureVolumeIndex reachable = NativeStructureVolumeIndex.forTesting(new CountingResolver(reach, 0));
NativeStructureVolumeIndex unreachable = NativeStructureVolumeIndex.forTesting(new CountingResolver(reach + 1, 0));
assertFalse(reachable.resolve(null, 0, 0, 15, 15).isEmpty());
assertTrue(unreachable.resolve(null, 0, 0, 15, 15).isEmpty());
}
@Test
public void overlappingColdQueriesCoordinateBeforeResolvingOrigins() throws Exception {
BlockingResolver resolver = new BlockingResolver();
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<KList<NativeStructureVolume>> first = executor.submit(() -> index.resolve(null, 0, 0, 15, 15));
assertTrue(resolver.awaitFirstEntry());
Future<KList<NativeStructureVolume>> adjacent = executor.submit(() -> index.resolve(null, 16, 0, 31, 15));
assertFalse(resolver.awaitSecondEntry(250));
resolver.release();
assertTrue(first.get(5, TimeUnit.SECONDS).isEmpty());
assertTrue(adjacent.get(5, TimeUnit.SECONDS).isEmpty());
assertEquals(306, resolver.resolutions());
} finally {
resolver.release();
executor.shutdownNow();
}
}
@Test
public void contendedNativeWindowsAllowQueuedTerrainWorkToRun() throws Exception {
assertQueuedTerrainCanComplete(false);
}
@Test
public void interruptedNativeWindowWaitStillAcquiresAndReleasesOnce() throws Exception {
assertQueuedTerrainCanComplete(true);
}
private static void assertQueuedTerrainCanComplete(boolean interruptWaiter) throws Exception {
CountDownLatch ownerEntered = new CountDownLatch(1);
CompletableFuture<Void> terrain = new CompletableFuture<>();
AtomicBoolean firstOrigin = new AtomicBoolean(true);
AtomicBoolean interruptedAfterQuery = new AtomicBoolean();
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
if (firstOrigin.compareAndSet(true, false)) {
ownerEntered.countDown();
terrain.join();
}
return new KList<>(new NativeStructureVolume(chunkX + ":" + chunkZ,
-256, 0, -256, 256, 100, 256));
});
ExecutorService owner = Executors.newSingleThreadExecutor();
ForkJoinPool workers = new ForkJoinPool(2);
AtomicReference<Thread> firstWaiter = new AtomicReference<>();
AtomicReference<Thread> secondWaiter = new AtomicReference<>();
try {
Future<KList<NativeStructureVolume>> original = owner.submit(
() -> index.resolve(null, 0, 0, 15, 15));
assertTrue(ownerEntered.await(5, TimeUnit.SECONDS));
Future<KList<NativeStructureVolume>> adjacent = workers.submit(() -> {
firstWaiter.set(Thread.currentThread());
KList<NativeStructureVolume> result = index.resolve(null, 16, 0, 31, 15);
interruptedAfterQuery.set(Thread.currentThread().isInterrupted());
return result;
});
Future<KList<NativeStructureVolume>> next = workers.submit(() -> {
secondWaiter.set(Thread.currentThread());
return index.resolve(null, 32, 0, 47, 15);
});
awaitOriginWindowWaiter(firstWaiter);
awaitOriginWindowWaiter(secondWaiter);
if (interruptWaiter) {
firstWaiter.get().interrupt();
}
Future<?> terrainTask = workers.submit(() -> terrain.complete(null));
terrainTask.get(3, TimeUnit.SECONDS);
assertEquals(289, original.get(3, TimeUnit.SECONDS).size());
KList<NativeStructureVolume> adjacentVolumes = adjacent.get(3, TimeUnit.SECONDS);
assertEquals(289, adjacentVolumes.size());
assertEquals(adjacentVolumes, index.resolve(null, 16, 0, 31, 15));
assertEquals(289, next.get(3, TimeUnit.SECONDS).size());
assertEquals(interruptWaiter, interruptedAfterQuery.get());
} finally {
terrain.complete(null);
owner.shutdownNow();
workers.shutdownNow();
assertTrue(owner.awaitTermination(5, TimeUnit.SECONDS));
assertTrue(workers.awaitTermination(5, TimeUnit.SECONDS));
}
}
private static void awaitOriginWindowWaiter(AtomicReference<Thread> reference) {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
while (System.nanoTime() < deadline) {
Thread thread = reference.get();
if (thread != null && thread.getState() == Thread.State.WAITING) {
for (StackTraceElement frame : thread.getStackTrace()) {
if (frame.getClassName().equals(NativeStructureVolumeIndex.class.getName())
&& frame.getMethodName().equals("lockOriginWindow")) {
return;
}
}
}
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
}
throw new AssertionError("Native query did not wait for the occupied origin window");
}
@Test
public void distantColdQueriesCanResolveOriginsConcurrently() throws Exception {
BlockingResolver resolver = new BlockingResolver();
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
int distantChunkX = findDisjointWindow(0, 0);
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<KList<NativeStructureVolume>> first = executor.submit(() -> index.resolve(null, 0, 0, 15, 15));
assertTrue(resolver.awaitFirstEntry());
Future<KList<NativeStructureVolume>> distant = executor.submit(
() -> index.resolve(null, distantChunkX << 4, 0, (distantChunkX << 4) + 15, 15));
assertTrue(resolver.awaitSecondEntry(5_000));
resolver.release();
assertTrue(first.get(5, TimeUnit.SECONDS).isEmpty());
assertTrue(distant.get(5, TimeUnit.SECONDS).isEmpty());
} finally {
resolver.release();
executor.shutdownNow();
}
}
@Test
public void coordinatedResolutionPreservesCanonicalOriginOrder() {
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(new OrderedResolver());
KList<NativeStructureVolume> volumes = index.resolve(null, 0, 0, 15, 15);
assertEquals(289, volumes.size());
assertEquals("-8:-8", volumes.getFirst().structure());
assertEquals("0:0", volumes.get(144).structure());
assertEquals("8:8", volumes.getLast().structure());
}
private static ScopedEngine scopedEngine(boolean scoped, int ownerRuntimeId) throws Exception {
IrisEngine engine = mock(IrisEngine.class);
GenerationHistoryRuntimeRouter router = mock(GenerationHistoryRuntimeRouter.class);
when(engine.hasGenerationRuntimeScope()).thenReturn(scoped);
when(engine.getGenerationHistoryRuntimeRouter()).thenReturn(Optional.of(router));
when(engine.getCacheID()).thenReturn(1);
if (ownerRuntimeId != 0) {
IrisEngine.GenerationRuntimeBinding binding = mock(IrisEngine.GenerationRuntimeBinding.class);
when(binding.runtimeId()).thenReturn(ownerRuntimeId);
when(router.currentRuntimeOwnership()).thenReturn(Optional.of(
new GenerationHistoryRuntimeRouter.RuntimeOwnership(1L, binding)));
}
AtomicInteger scopes = new AtomicInteger();
when(engine.openGenerationHistoryCoordinateScope(anyInt(), anyInt())).thenAnswer(invocation -> {
scopes.incrementAndGet();
return null;
});
return new ScopedEngine(engine, scopes);
}
@SuppressWarnings("unchecked")
private static Map<Engine, NativeStructureVolumeIndex> installedIndexes() throws Exception {
Field indexes = NativeStructureVolumeIndex.class.getDeclaredField("INDEXES");
indexes.setAccessible(true);
return (Map<Engine, NativeStructureVolumeIndex>) indexes.get(null);
}
private static Map<?, ?> cache(NativeStructureVolumeIndex index, String name) throws Exception {
Field field = NativeStructureVolumeIndex.class.getDeclaredField(name);
field.setAccessible(true);
return (Map<?, ?>) field.get(index);
}
private static StructureStart swampHut(int chunkX, int chunkZ, int x, int z) {
Structure source = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(17L), x, z);
return new StructureStart(source, new ChunkPos(chunkX, chunkZ), 0, new PiecesContainer(List.of(piece)));
}
private static Engine runtimeEngine(AtomicInteger runtimeId) {
return (Engine) Proxy.newProxyInstance(
Engine.class.getClassLoader(),
new Class<?>[]{Engine.class},
(proxy, method, arguments) -> {
if (method.getName().equals("getCacheID")) {
return runtimeId.get();
}
if (method.getName().equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (method.getName().equals("equals")) {
return proxy == arguments[0];
}
if (method.getName().equals("toString")) {
return "runtime-engine-" + runtimeId.get();
}
throw new UnsupportedOperationException(method.getName());
}
);
}
private static int findDisjointWindow(int chunkX, int chunkZ) {
long sourceMask = NativeStructureVolumeIndex.originWindowStripeMask(chunkX, chunkZ);
for (int candidate = 64; candidate < 16_384; candidate += 17) {
if ((sourceMask & NativeStructureVolumeIndex.originWindowStripeMask(candidate, chunkZ)) == 0L) {
return candidate;
}
}
throw new IllegalStateException("Unable to find a disjoint native structure window stripe");
}
private static final class CountingResolver implements NativeStructureVolumeIndex.OriginResolver {
private final AtomicInteger resolutions = new AtomicInteger();
private final int originChunkX;
private final int originChunkZ;
private CountingResolver(int originChunkX, int originChunkZ) {
this.originChunkX = originChunkX;
this.originChunkZ = originChunkZ;
}
private int resolutions() {
return resolutions.get();
}
@Override
public KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ) {
resolutions.incrementAndGet();
if (chunkX != originChunkX || chunkZ != originChunkZ) {
return NativeStructureVolume.NONE;
}
return NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
}
}
private static final class BlockingResolver implements NativeStructureVolumeIndex.OriginResolver {
private final AtomicInteger resolutions = new AtomicInteger();
private final CountDownLatch firstEntry = new CountDownLatch(1);
private final CountDownLatch secondEntry = new CountDownLatch(1);
private final CountDownLatch release = new CountDownLatch(1);
private int resolutions() {
return resolutions.get();
}
private boolean awaitFirstEntry() throws InterruptedException {
return firstEntry.await(5, TimeUnit.SECONDS);
}
private boolean awaitSecondEntry(long timeoutMillis) throws InterruptedException {
return secondEntry.await(timeoutMillis, TimeUnit.MILLISECONDS);
}
private void release() {
release.countDown();
}
@Override
public KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ) {
int resolution = resolutions.incrementAndGet();
if (resolution == 1) {
firstEntry.countDown();
try {
if (!release.await(5, TimeUnit.SECONDS)) {
throw new IllegalStateException("Timed out waiting to release native structure resolution");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while coordinating native structure resolution", e);
}
} else if (resolution == 2) {
secondEntry.countDown();
}
return NativeStructureVolume.NONE;
}
}
private static final class OrderedResolver implements NativeStructureVolumeIndex.OriginResolver {
@Override
public KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ) {
return new KList<>(new NativeStructureVolume(
chunkX + ":" + chunkZ,
0, 0, 0,
15, 255, 15));
}
}
private record ScopedEngine(IrisEngine engine, AtomicInteger scopes) {
}
}
@@ -0,0 +1,129 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.Heightmap;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.OptionalInt;
import java.util.function.IntSupplier;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public final class NativeTerrainHeightCacheTest {
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void coordinatesRuntimePredicatesAndBoundsHaveSeparateEntries() {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
AtomicInteger calls = new AtomicInteger();
NativeTerrainHeightCache.Query[] queries = {
query(1, -1, 1),
query(1, 1, -1),
query(2, -1, 1),
new NativeTerrainHeightCache.Query(1, -1, 1, Heightmap.Types.OCEAN_FLOOR, -256, 768),
new NativeTerrainHeightCache.Query(1, -1, 1, Heightmap.Types.WORLD_SURFACE, -64, 768),
new NativeTerrainHeightCache.Query(1, -1, 1, Heightmap.Types.WORLD_SURFACE, -256, 256)
};
for (int index = 0; index < queries.length; index++) {
assertEquals(index + 1, cached(cache, queries[index], calls::incrementAndGet));
}
for (int index = 0; index < queries.length; index++) {
assertEquals(index + 1, cached(cache, queries[index], calls::incrementAndGet));
}
assertEquals(queries.length, calls.get());
cache.evictRuntime(1);
assertEquals(3, cached(cache, queries[2], calls::incrementAndGet));
assertEquals(7, cached(cache, queries[0], calls::incrementAndGet));
}
@Test
public void allIntegerResultsAreCacheable() {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
NativeTerrainHeightCache.Query query = query(1, 0, 0);
assertEquals(Integer.MIN_VALUE, cached(cache, query, () -> Integer.MIN_VALUE));
assertEquals(Integer.MIN_VALUE, cached(cache, query, () -> 0));
}
@Test
public void failedResolutionCanBeRetried() {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
NativeTerrainHeightCache.Query query = query(1, 0, 0);
assertThrows(IllegalStateException.class, () -> cached(cache, query, () -> {
throw new IllegalStateException("terrain unavailable");
}));
assertEquals(42, cached(cache, query, () -> 42));
}
@Test
public void coldResolutionDoesNotHoldCacheMonitor() throws Exception {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
CountDownLatch entered = new CountDownLatch(1);
CountDownLatch release = new CountDownLatch(1);
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<Integer> slow = executor.submit(() -> cached(cache, query(1, 0, 0), () -> {
entered.countDown();
try {
assertTrue(release.await(5, TimeUnit.SECONDS));
} catch (InterruptedException interruption) {
Thread.currentThread().interrupt();
throw new IllegalStateException(interruption);
}
return 9;
}));
assertTrue(entered.await(5, TimeUnit.SECONDS));
Future<Integer> independent = executor.submit(() -> cached(cache, query(1, 1, 0), () -> 17));
assertEquals(Integer.valueOf(17), independent.get(1, TimeUnit.SECONDS));
release.countDown();
assertEquals(Integer.valueOf(9), slow.get(5, TimeUnit.SECONDS));
} finally {
release.countDown();
executor.shutdownNow();
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
}
}
@Test
public void boundedCacheKeepsRecentlyReadCoordinates() {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
for (int x = 0; x < 65_536; x++) {
cached(cache, query(1, x, 0), () -> 8);
}
assertEquals(8, cached(cache, query(1, 0, 0), () -> 99));
cached(cache, query(1, 65_536, 0), () -> 9);
assertEquals(8, cached(cache, query(1, 0, 0), () -> 99));
assertEquals(99, cached(cache, query(1, 1, 0), () -> 99));
}
@Test
public void unavailableResolvedHeightIsNotCached() {
NativeTerrainHeightCache cache = new NativeTerrainHeightCache();
NativeTerrainHeightCache.Query query = query(1, 0, 0);
assertTrue(cache.resolvedHeight(query, OptionalInt::empty).isEmpty());
assertEquals(42, cache.resolvedHeight(query, () -> OptionalInt.of(42)).orElseThrow());
assertEquals(42, cache.resolvedHeight(query, OptionalInt::empty).orElseThrow());
}
private static int cached(NativeTerrainHeightCache cache, NativeTerrainHeightCache.Query query,
IntSupplier resolver) {
return cache.resolvedHeight(query, () -> OptionalInt.of(resolver.getAsInt())).orElseThrow();
}
private static NativeTerrainHeightCache.Query query(int runtimeId, int x, int z) {
return new NativeTerrainHeightCache.Query(runtimeId, x, z, Heightmap.Types.WORLD_SURFACE, -256, 768);
}
}
@@ -0,0 +1,105 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.world.history.BoundaryColumnGeometry;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RotatedPillarBlock;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.levelgen.Heightmap;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.util.List;
import java.util.OptionalInt;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public final class NativeTransitionColumnTest {
@Rule
public final PlatformLeakGuard leakGuard = PlatformLeakGuard.clean();
@BeforeClass
public static void bootstrap() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void customKeysUseCarrierForNativeColumnsAndHeightmaps() {
String key = "itemsadder:rocks/ruby_ore";
PlatformBlockState custom = mock(PlatformBlockState.class);
PlatformBlockState carrier = mock(PlatformBlockState.class);
when(custom.placementBaseState()).thenReturn(carrier);
when(carrier.key()).thenReturn("minecraft:oak_log[axis=z]");
IrisPlatform platform = mock(IrisPlatform.class);
PlatformRegistries registries = mock(PlatformRegistries.class);
when(platform.registries()).thenReturn(registries);
when(registries.blockOrNull(key)).thenReturn(custom);
LevelHeightAccessor height = LevelHeightAccessor.create(-16, 32);
TerrainBoundarySignature signature = signature(key);
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
platforms.when(IrisPlatforms::get).thenReturn(platform);
NoiseColumn column = NativeTransitionColumn.column(signature, height);
assertEquals(Direction.Axis.Z, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
}
}
@Test
public void preservesNativePropertiesCavesAndFluids() {
NoiseColumn column = NativeTransitionColumn.column(signature("minecraft:oak_log[axis=x]"),
LevelHeightAccessor.create(-16, 32));
assertEquals(Direction.Axis.X, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertTrue(column.getBlock(-14).is(Blocks.CAVE_AIR));
assertEquals(Integer.valueOf(3), column.getBlock(-13).getValue(LiquidBlock.LEVEL));
assertTrue(column.getBlock(0).isAir());
}
@Test
public void usesNativeHeightmapPredicatesForWaterAndOpenSpace() {
TerrainBoundarySignature signature = signature("minecraft:oak_log[axis=x]");
LevelHeightAccessor height = LevelHeightAccessor.create(-16, 32);
assertEquals(-12, NativeTransitionColumn.height(signature, Heightmap.Types.WORLD_SURFACE_WG, height));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
}
@Test
public void rejectsUnknownSavedStatesInsteadOfReplacingTerrain() {
assertThrows(IllegalArgumentException.class, () -> NativeTransitionColumn.column(
signature("missing:removed_block"), LevelHeightAccessor.create(-16, 32)));
}
private static TerrainBoundarySignature signature(String solidState) {
BoundaryColumnGeometry geometry = BoundaryColumnGeometry.fromVoxels(-16, List.of(
new BoundaryColumnGeometry.Voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", false),
new BoundaryColumnGeometry.Voxel(solidState, BoundaryColumnGeometry.Phase.SOLID, "", false),
new BoundaryColumnGeometry.Voxel("minecraft:cave_air", BoundaryColumnGeometry.Phase.AIR, "", false),
new BoundaryColumnGeometry.Voxel("minecraft:water[level=3]", BoundaryColumnGeometry.Phase.FLUID,
"minecraft:water[level=3]", false)));
return new TerrainBoundarySignature(
new TerrainBoundarySignature.Column(0, 0, 3, 1, OptionalInt.of(3), OptionalInt.empty()),
new TerrainBoundarySignature.Samples(new TerrainBoundarySignature.VerticalLayout(-16, 4, 1),
new TerrainBoundarySignature.BiomeEncoding(List.of("minecraft:plains"), new short[]{0})),
geometry);
}
}
@@ -0,0 +1,64 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Proxy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class CustomBiomeSourceStructureContractTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void stackedCustomBiomesUseTheirOwningDimensionNamespace() {
IrisDimension host = new IrisDimension();
host.setLoadKey("Host");
IrisDimension upper = new IrisDimension();
upper.setLoadKey("Layers/Upper");
assertEquals("layers:upper/aurora", CustomBiomeSource.customBiomeKey(upper, "Aurora"));
assertNotEquals(
CustomBiomeSource.customBiomeKey(host, "Aurora"),
CustomBiomeSource.customBiomeKey(upper, "Aurora")
);
}
@Test
public void temporaryNaturalBiomeAnswersAreNotMemoized() {
Engine temporary = engineAnsweringNaturalFallback(true, 12, -8);
Engine stable = engineAnsweringNaturalFallback(false, 12, -8);
assertFalse(CustomBiomeSource.isBiomeCacheable(temporary, 12, -8));
assertTrue(CustomBiomeSource.isBiomeCacheable(stable, 12, -8));
}
private static Engine engineAnsweringNaturalFallback(
boolean naturalFallback,
int expectedX,
int expectedZ
) {
return (Engine) Proxy.newProxyInstance(
Engine.class.getClassLoader(),
new Class<?>[]{Engine.class},
(proxy, method, arguments) -> {
if (!method.getName().equals("answersFromNaturalTerrain")) {
throw new UnsupportedOperationException(method.getName());
}
assertEquals(Integer.valueOf(expectedX), arguments[0]);
assertEquals(Integer.valueOf(expectedZ), arguments[1]);
return naturalFallback;
}
);
}
}
@@ -0,0 +1,97 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import org.junit.Test;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisChunkGeneratorFailureContractTest {
@Test
public void releasedNoiseAdmissionLetsSynchronousDependentWaitForQueuedExclusive() throws Exception {
Semaphore admission = new Semaphore(1, true);
admission.acquire();
CountDownLatch exclusiveEntered = new CountDownLatch(1);
CountDownLatch releaseExclusive = new CountDownLatch(1);
AtomicBoolean dependentFinished = new AtomicBoolean(false);
ExecutorService executor = Executors.newSingleThreadExecutor();
boolean stageReleased = false;
try {
Future<?> exclusive = executor.submit(() -> {
boolean acquired = false;
try {
admission.acquire();
acquired = true;
exclusiveEntered.countDown();
assertTrue(releaseExclusive.await(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
} finally {
if (acquired) {
admission.release();
}
}
});
awaitQueueLength(admission, 1);
CompletableFuture<Void> outward = new CompletableFuture<>();
outward.thenRun(() -> {
try {
assertTrue(exclusiveEntered.await(2, TimeUnit.SECONDS));
dependentFinished.set(true);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
});
admission.release();
stageReleased = true;
assertTrue(outward.complete(null));
assertTrue(dependentFinished.get());
releaseExclusive.countDown();
exclusive.get(2, TimeUnit.SECONDS);
assertEquals(1, admission.availablePermits());
} finally {
if (!stageReleased) {
admission.release();
}
releaseExclusive.countDown();
executor.shutdownNow();
}
}
@Test
public void transformedDelegateCancellationIsDetectedThroughItsCompletionFailure() {
CompletableFuture<Void> delegate = new CompletableFuture<>();
CompletableFuture<Void> transformed = delegate.thenApply(value -> value);
assertTrue(delegate.cancel(false));
assertFalse(transformed.isCancelled());
CompletionException failure = assertThrows(CompletionException.class, transformed::join);
assertTrue(failure.getCause() instanceof CancellationException);
}
private static void awaitQueueLength(Semaphore semaphore, int expected) {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2);
while (semaphore.getQueueLength() < expected && System.nanoTime() < deadline) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
}
assertTrue("Expected at least " + expected + " queued semaphore threads",
semaphore.getQueueLength() >= expected);
}
}
@@ -0,0 +1,58 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class IrisChunkGeneratorMonumentLocateContractTest {
@Test
public void mixedLocateSelectsNearestProviderAndPrefersNativeOnTie() {
BlockPos origin = BlockPos.ZERO;
Pair<BlockPos, Holder<Structure>> irisNear = Pair.of(new BlockPos(4, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeFar = Pair.of(new BlockPos(8, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeNear = Pair.of(new BlockPos(2, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeTie = Pair.of(new BlockPos(0, 70, 4), null);
assertSame(irisNear, NativeStructureLocateResults.nearest(origin, irisNear, nativeFar));
assertSame(nativeNear, NativeStructureLocateResults.nearest(origin, irisNear, nativeNear));
assertSame(nativeTie, NativeStructureLocateResults.nearest(origin, irisNear, nativeTie));
}
@Test
public void mixedUnexploredLocateReferencesOnlyTheSelectedProvider() {
BlockPos origin = BlockPos.ZERO;
Pair<BlockPos, Holder<Structure>> irisNear = Pair.of(new BlockPos(4, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeFar = Pair.of(new BlockPos(8, 70, 0), null);
AtomicInteger irisReferences = new AtomicInteger();
AtomicInteger nativeReferences = new AtomicInteger();
Pair<BlockPos, Holder<Structure>> irisSelected =
NativeStructureLocateResults.selectAndReference(
origin,
irisNear, () -> irisReferences.incrementAndGet(),
nativeFar, () -> nativeReferences.incrementAndGet());
assertSame(irisNear, irisSelected);
assertEquals(1, irisReferences.get());
assertEquals(0, nativeReferences.get());
Pair<BlockPos, Holder<Structure>> nativeNear = Pair.of(new BlockPos(2, 70, 0), null);
Pair<BlockPos, Holder<Structure>> nativeSelected =
NativeStructureLocateResults.selectAndReference(
origin,
irisNear, () -> irisReferences.incrementAndGet(),
nativeNear, () -> nativeReferences.incrementAndGet());
assertSame(nativeNear, nativeSelected);
assertEquals(1, irisReferences.get());
assertEquals(1, nativeReferences.get());
}
}
@@ -0,0 +1,144 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.GenerationSessionManager;
import art.arcane.iris.generation.runtime.GenerationTransitionGate;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.world.history.NativeBiomeSpawnSelection;
import art.arcane.iris.world.history.SavedBiomeRuntime;
import art.arcane.iris.generation.context.IrisContext;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.random.WeightedList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.chunk.ChunkGenerator;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisChunkGeneratorSpawnAdmissionTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void tickSpawnQueryLeavesOwnerThreadAvailableForBoundaryCheckpoint() throws Exception {
Fixture fixture = new Fixture();
ExecutorService owner = Executors.newSingleThreadExecutor();
ExecutorService reloader = Executors.newSingleThreadExecutor();
AtomicInteger snapshots = new AtomicInteger();
long previousSession = fixture.sessions.currentSessionId();
try {
assertSame(fixture.expected, owner.submit(fixture::query).get(2L, TimeUnit.SECONDS));
assertEquals(previousSession, fixture.readSession.get());
CompletableFuture<Void> cutover = CompletableFuture.runAsync(() -> {
try (GenerationTransitionGate.Transition ignored = fixture.sessions.transitionGate().beginTransition(2_000L)) {
fixture.sessions.sealAndAwait("Studio generation cutover", 2_000L);
Future<?> query = owner.submit(() -> {
assertTrue(fixture.query().isEmpty());
assertNull(IrisContext.get());
});
owner.submit(snapshots::incrementAndGet).get(2L, TimeUnit.SECONDS);
query.get(2L, TimeUnit.SECONDS);
assertEquals(1, fixture.savedReads.get());
assertEquals(1, fixture.routes.get());
fixture.sessions.activateNextSession();
} catch (Exception failure) {
throw new IllegalStateException(failure);
}
}, reloader);
cutover.get(3L, TimeUnit.SECONDS);
assertEquals(1, snapshots.get());
assertEquals(0, fixture.sessions.activeLeases());
assertSame(fixture.expected, owner.submit(fixture::query).get(2L, TimeUnit.SECONDS));
assertEquals(fixture.sessions.currentSessionId(), fixture.readSession.get());
assertTrue(fixture.readSession.get() > previousSession);
assertEquals(2, fixture.savedReads.get());
assertEquals(2, fixture.routes.get());
assertEquals(0, fixture.sessions.activeLeases());
} finally {
owner.shutdownNow();
reloader.shutdownNow();
assertTrue(owner.awaitTermination(2L, TimeUnit.SECONDS));
assertTrue(reloader.awaitTermination(2L, TimeUnit.SECONDS));
}
}
private static void setField(IrisChunkGenerator generator, String name, Object value) throws Exception {
Field field = IrisChunkGenerator.class.getDeclaredField(name);
field.setAccessible(true);
field.set(generator, value);
}
private static final class Fixture {
private final GenerationSessionManager sessions = new GenerationSessionManager(true);
private final IrisEngine engine = mock(IrisEngine.class);
private final IrisChunkGenerator generator = mock(IrisChunkGenerator.class, CALLS_REAL_METHODS);
private final Holder<Biome> biome = Holder.direct(mock(Biome.class));
private final StructureManager structures = mock(StructureManager.class);
private final WeightedList<MobSpawnSettings.SpawnerData> expected = mock(WeightedList.class);
private final AtomicInteger savedReads = new AtomicInteger();
private final AtomicInteger routes = new AtomicInteger();
private final AtomicLong readSession = new AtomicLong();
private Fixture() throws Exception {
GenerationHistoryRuntimeRouter router = mock(GenerationHistoryRuntimeRouter.class);
SavedBiomeRuntime saved = mock(SavedBiomeRuntime.class);
ChunkGenerator delegate = mock(ChunkGenerator.class);
CustomBiomeSource source = mock(CustomBiomeSource.class);
when(engine.getGenerationSessions()).thenReturn(sessions);
when(engine.getGenerationHistoryRuntimeRouter()).thenReturn(Optional.of(router));
when(router.biomes()).thenReturn(saved);
when(saved.nativeSpawnSelection(0, 64, 0, "")).thenAnswer(invocation -> {
assertAdmitted();
savedReads.incrementAndGet();
return new NativeBiomeSpawnSelection(NativeBiomeSpawnSelection.Mode.RETAINED, "minecraft:plains");
});
when(engine.openGenerationHistoryCoordinateScope(0, 0)).thenAnswer(invocation -> {
assertAdmitted();
routes.incrementAndGet();
return null;
});
when(delegate.getMobsAt(biome, structures, MobCategory.MONSTER, new BlockPos(0, 64, 0)))
.thenReturn(expected);
setField(generator, "engine", engine);
setField(generator, "delegate", delegate);
setField(generator, "customBiomeSource", source);
}
private WeightedList<MobSpawnSettings.SpawnerData> query() {
return generator.getMobsAt(biome, structures, MobCategory.MONSTER, new BlockPos(0, 64, 0));
}
private void assertAdmitted() {
assertEquals(1, sessions.activeLeases());
assertSame(engine, IrisContext.get().getEngine());
readSession.set(IrisContext.get().getGenerationSessionId());
}
}
}
@@ -0,0 +1,509 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.pack.datapack.DatapackIngestService;
import art.arcane.iris.pack.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.structure.nativegen.IrisImportedStructureControl;
import art.arcane.iris.structure.placement.IrisStructureSetFrequencyOverride;
import art.arcane.volmlib.util.collection.KList;
import com.mojang.datafixers.util.Either;
import net.minecraft.SharedConstants;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.Vec3i;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacementType;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import org.junit.Test;
import org.junit.BeforeClass;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
public class NMSBindingDatapackStructureScopeTest {
private static final String SOURCE = "https://example.test/managed.zip";
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void managedSetContainingVanillaStructureIsAbsentOutsideDeclaringDimension() {
Holder<Structure> vanillaStructure = structureHolder("minecraft:pillager_outpost");
Holder<StructureSet> managedSet = structureSetHolder(
"managed:illager_barracks", vanillaStructure);
DatapackStructureScopeIndex index = index(
List.of(),
List.of("managed:illager_barracks"));
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
List.of(managedSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
List.of(managedSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(0, vanilla.structureSets().size());
assertEquals(1, vanilla.excludedManagedSets());
assertEquals(1, declaring.structureSets().size());
assertSame(managedSet, declaring.structureSets().getFirst());
}
@Test
public void unmanagedSetRetainsOnlyDefinitionsAllowedInTheWorld() {
Holder<Structure> vanillaStructure = structureHolder("minecraft:village_plains");
Holder<Structure> managedStructure = structureHolder("managed:tavern");
Holder<StructureSet> vanillaSet = structureSetHolder(
"minecraft:villages", vanillaStructure, managedStructure);
DatapackStructureScopeIndex index = index(
List.of("managed:tavern"),
List.of());
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
List.of(vanillaSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
List.of(vanillaSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(1, vanilla.structureSets().size());
assertEquals(1, vanilla.structureSets().getFirst().value().structures().size());
assertSame(vanillaStructure,
vanilla.structureSets().getFirst().value().structures().getFirst().structure());
assertSame(vanillaSet.value().placement(),
vanilla.structureSets().getFirst().value().placement());
assertSame(vanillaSet, declaring.structureSets().getFirst());
}
@Test
public void setWithNoAllowedDefinitionsIsRemoved() {
Holder<StructureSet> unmanagedSet = structureSetHolder(
"minecraft:custom", structureHolder("managed:only"));
DatapackStructureScopeIndex index = index(List.of("managed:only"), List.of());
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(unmanagedSet), index, Set.of());
assertEquals(0, selection.structureSets().size());
}
@Test
public void spigotDirectHolderRecoversItsRegistryKeyFromSharedEntries() {
ResourceKey<StructureSet> key = ResourceKey.create(
Registries.STRUCTURE_SET,
Identifier.parse("managed:illager_barracks"));
List<StructureSet.StructureSelectionEntry> entries = List.of(
new StructureSet.StructureSelectionEntry(
structureHolder("minecraft:pillager_outpost"), 1));
Holder<StructureSet> registered = new KeyedHolder<>(key, new StructureSet(
entries,
new RandomSpreadStructurePlacement(
34, 8, RandomSpreadType.LINEAR, 10387312)));
Holder<StructureSet> spigotDirect = Holder.direct(new StructureSet(
entries,
new RandomSpreadStructurePlacement(
34, 8, RandomSpreadType.LINEAR, 14357620)));
DatapackStructureScopeIndex scopeIndex = index(
List.of(), List.of("managed:illager_barracks"));
DatapackStructureStateFilter.StructureSetKeyIndex keyIndex =
DatapackStructureStateFilter.keyIndex(List.of(registered));
DatapackStructureStateFilter.Selection excluded =
DatapackStructureStateFilter.filter(
List.of(spigotDirect),
scopeIndex,
Set.of(),
new IrisImportedStructureControl(),
keyIndex);
DatapackStructureStateFilter.Selection retained =
DatapackStructureStateFilter.filter(
List.of(spigotDirect),
scopeIndex,
scopeIndex.declaredSources(List.of(SOURCE)),
new IrisImportedStructureControl(),
keyIndex);
assertEquals(0, excluded.structureSets().size());
assertEquals(1, excluded.excludedManagedSets());
assertSame(spigotDirect, retained.structureSets().getFirst());
assertEquals(1, retained.retainedManagedSets());
}
@Test
public void structureScopeDoesNotLinkPaperOnlyPlacementClasses() throws IOException {
InputStream classResource = NMSBindingDatapackStructureScopeTest.class
.getResourceAsStream("DatapackStructureStateFilter.class");
assertNotNull(classResource);
try (InputStream input = classResource) {
String classFile = new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
assertFalse(classFile.contains("KeyedRandomSpreadStructurePlacement"));
}
}
@Test
public void exactFrequencyOverridesScaleOnlyTheNamedNativeStructureSet() {
Holder<StructureSet> complexes = structureSetHolder(
"minecraft:nether_complexes",
new RandomSpreadStructurePlacement(27, 4, RandomSpreadType.LINEAR, 30084232),
structureHolder("minecraft:fortress"));
Holder<StructureSet> fossils = structureSetHolder(
"minecraft:nether_fossils",
new RandomSpreadStructurePlacement(2, 1, RandomSpreadType.LINEAR, 14357921),
structureHolder("minecraft:nether_fossil"));
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
overrides.add(new IrisStructureSetFrequencyOverride()
.setStructureSet("minecraft:nether_complexes")
.setMultiplier(1.1D));
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(complexes, fossils), index(List.of(), List.of()), Set.of(), control);
RandomSpreadStructurePlacement scaledComplexes =
(RandomSpreadStructurePlacement) selection.structureSets().get(0).value().placement();
RandomSpreadStructurePlacement unchangedFossils =
(RandomSpreadStructurePlacement) selection.structureSets().get(1).value().placement();
assertEquals(26, scaledComplexes.spacing());
assertEquals(4, scaledComplexes.separation());
assertEquals(2, unchangedFossils.spacing());
assertSame(fossils, selection.structureSets().get(1));
assertEquals(27, ((RandomSpreadStructurePlacement) complexes.value().placement()).spacing());
}
@Test
public void frequencyOnlyPassLeavesUnrelatedCustomPlacementUntouched() {
Holder<StructureSet> exclusionTarget = structureSetHolder(
"example:target", structureHolder("example:target"));
Holder<StructureSet> custom = structureSetHolder(
"example:custom",
new UnsupportedPlacement(Optional.of(
new StructurePlacement.ExclusionZone(exclusionTarget, 1))),
structureHolder("example:custom"));
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
overrides.add(new IrisStructureSetFrequencyOverride()
.setStructureSet("minecraft:nether_complexes")
.setMultiplier(1.1D));
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(custom), index(List.of(), List.of()), Set.of(), control);
assertSame(custom, selection.structureSets().getFirst());
}
@Test
public void frequencyOnlyPassRebindsDependentExclusionTarget() {
Holder<StructureSet> target = structureSetHolder(
"example:target",
new RandomSpreadStructurePlacement(27, 4, RandomSpreadType.LINEAR, 30084232),
structureHolder("example:target"));
RandomSpreadStructurePlacement dependentPlacement = new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
4567,
Optional.of(new StructurePlacement.ExclusionZone(target, 1)),
32,
8,
RandomSpreadType.LINEAR);
Holder<StructureSet> dependent = structureSetHolder(
"example:dependent",
dependentPlacement,
structureHolder("example:dependent"));
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
overrides.add(new IrisStructureSetFrequencyOverride()
.setStructureSet("example:target")
.setMultiplier(1.1D));
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(dependent, target), index(List.of(), List.of()), Set.of(), control);
Holder<StructureSet> scaledDependent = selection.structureSets().get(0);
Holder<StructureSet> scaledTarget = selection.structureSets().get(1);
assertNotSame(dependent, scaledDependent);
assertNotSame(target, scaledTarget);
assertSame(scaledTarget, DatapackStructureStateFilter.exclusionZone(
scaledDependent.value().placement()).orElseThrow().otherSet());
assertEquals(26, ((RandomSpreadStructurePlacement)
scaledTarget.value().placement()).spacing());
}
@Test
public void frequencyOnlyPassRebindsAffectedExclusionCycle() {
MutableStructureSetHolder first = new MutableStructureSetHolder("example:first");
MutableStructureSetHolder second = new MutableStructureSetHolder("example:second");
first.bind(structureSet(
new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
101,
Optional.of(new StructurePlacement.ExclusionZone(second, 1)),
32,
8,
RandomSpreadType.LINEAR),
"example:first"));
second.bind(structureSet(
new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
102,
Optional.of(new StructurePlacement.ExclusionZone(first, 1)),
27,
4,
RandomSpreadType.LINEAR),
"example:second"));
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
overrides.add(new IrisStructureSetFrequencyOverride()
.setStructureSet("example:second")
.setMultiplier(1.1D));
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(first, second), index(List.of(), List.of()), Set.of(), control);
Holder<StructureSet> scaledFirst = selection.structureSets().get(0);
Holder<StructureSet> scaledSecond = selection.structureSets().get(1);
assertSame(scaledSecond, DatapackStructureStateFilter.exclusionZone(
scaledFirst.value().placement()).orElseThrow().otherSet());
assertSame(scaledFirst, DatapackStructureStateFilter.exclusionZone(
scaledSecond.value().placement()).orElseThrow().otherSet());
assertEquals(26, ((RandomSpreadStructurePlacement)
scaledSecond.value().placement()).spacing());
}
@Test
public void randomSpreadSubtypeUsesTheCanonicalPlacementContract() {
Holder<StructureSet> custom = structureSetHolder(
"example:custom",
new CustomRandomSpreadPlacement(),
structureHolder("example:custom"));
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
overrides.add(new IrisStructureSetFrequencyOverride()
.setStructureSet("example:custom")
.setMultiplier(1.1D));
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(custom), index(List.of(), List.of()), Set.of(), control);
StructurePlacement placement = selection.structureSets().getFirst().value().placement();
assertEquals(RandomSpreadStructurePlacement.class, placement.getClass());
assertEquals(26, ((RandomSpreadStructurePlacement) placement).spacing());
}
@Test
public void excludedManagedSetCannotSuppressAnAllowedSetThroughExclusionZone() {
Holder<StructureSet> managedSet = structureSetHolder(
"managed:blocked",
structureHolder("managed:blocked"));
RandomSpreadStructurePlacement originalPlacement = new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
4567,
Optional.of(new StructurePlacement.ExclusionZone(managedSet, 1)),
32,
8,
RandomSpreadType.LINEAR);
Holder<StructureSet> vanillaSet = structureSetHolder(
"minecraft:allowed",
originalPlacement,
structureHolder("minecraft:village_plains"));
DatapackStructureScopeIndex index = index(
List.of("managed:blocked"),
List.of("managed:blocked"));
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(vanillaSet, managedSet), index, Set.of());
assertEquals(1, selection.structureSets().size());
StructurePlacement scopedPlacement = selection.structureSets().getFirst().value().placement();
assertEquals(0, DatapackStructureStateFilter.exclusionZone(scopedPlacement).stream().count());
}
private static DatapackStructureScopeIndex index(
List<String> structureKeys,
List<String> structureSetKeys
) {
return DatapackStructureScopeIndex.create(List.of(
new DatapackIngestService.StructureScopeResources(
SOURCE,
structureKeys,
structureSetKeys)));
}
private static Holder<Structure> structureHolder(String key) {
return new KeyedHolder<>(ResourceKey.create(Registries.STRUCTURE, Identifier.parse(key)), null);
}
private static Holder<StructureSet> structureSetHolder(
String key,
Holder<Structure>... structures
) {
return structureSetHolder(
key,
new RandomSpreadStructurePlacement(32, 8, RandomSpreadType.LINEAR, 12345),
structures);
}
private static Holder<StructureSet> structureSetHolder(
String key,
StructurePlacement placement,
Holder<Structure>... structures
) {
List<StructureSet.StructureSelectionEntry> entries = Stream.of(structures)
.map(structure -> new StructureSet.StructureSelectionEntry(structure, 1))
.toList();
StructureSet value = new StructureSet(entries, placement);
return new KeyedHolder<>(
ResourceKey.create(Registries.STRUCTURE_SET, Identifier.parse(key)),
value);
}
private static StructureSet structureSet(StructurePlacement placement, String structureKey) {
return new StructureSet(
List.of(new StructureSet.StructureSelectionEntry(
structureHolder(structureKey), 1)),
placement);
}
private static final class KeyedHolder<T> implements Holder<T> {
private final ResourceKey<T> key;
private final T value;
private KeyedHolder(ResourceKey<T> key, T value) {
this.key = key;
this.value = value;
}
@Override
public T value() {
return value;
}
@Override
public boolean isBound() {
return true;
}
@Override
public boolean areComponentsBound() {
return true;
}
@Override
public boolean is(Identifier identifier) {
return key.identifier().equals(identifier);
}
@Override
public boolean is(ResourceKey<T> candidate) {
return key.equals(candidate);
}
@Override
public boolean is(Predicate<ResourceKey<T>> predicate) {
return predicate.test(key);
}
@Override
public boolean is(TagKey<T> tag) {
return false;
}
@Override
public boolean is(Holder<T> holder) {
return holder == this;
}
@Override
public Stream<TagKey<T>> tags() {
return Stream.empty();
}
@Override
public DataComponentMap components() {
return DataComponentMap.EMPTY;
}
@Override
public Either<ResourceKey<T>, T> unwrap() {
return Either.left(key);
}
@Override
public Optional<ResourceKey<T>> unwrapKey() {
return Optional.of(key);
}
@Override
public Kind kind() {
return Kind.REFERENCE;
}
@Override
public boolean canSerializeIn(HolderOwner<T> owner) {
return true;
}
}
private static final class UnsupportedPlacement extends StructurePlacement {
private UnsupportedPlacement(Optional<ExclusionZone> exclusionZone) {
super(Vec3i.ZERO, FrequencyReductionMethod.DEFAULT, 1F, 1, exclusionZone);
}
@Override
protected boolean isPlacementChunk(ChunkGeneratorStructureState state, int x, int z) {
return false;
}
@Override
public StructurePlacementType<?> type() {
return null;
}
}
private static final class MutableStructureSetHolder extends Holder.Reference<StructureSet> {
private MutableStructureSetHolder(String key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, ResourceKey.create(Registries.STRUCTURE_SET, Identifier.parse(key)), null);
}
private void bind(StructureSet structureSet) {
bindValue(structureSet);
}
}
private static final class CustomRandomSpreadPlacement extends RandomSpreadStructurePlacement {
private CustomRandomSpreadPlacement() {
super(27, 4, RandomSpreadType.LINEAR, 30084232);
}
}
}
@@ -0,0 +1,250 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.IdMapper;
import net.minecraft.core.SectionPos;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import net.minecraft.world.entity.ai.village.poi.PoiSection;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
import org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NativeStructurePoiUpdatesTest {
private static final NamespacedKey DIRTY_SECTIONS = new NamespacedKey("iris", "native_poi_sections");
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void generationRecordsIntentBeforeSuppressingNativePoiCallbacks() {
Fixture fixture = new Fixture();
BlockPos position = new BlockPos(-31, 5, 49);
doAnswer(call -> {
assertNotNull(fixture.proto.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY));
assertEquals(Block.UPDATE_CLIENTS | 4096, (int) call.getArgument(2));
fixture.proto.setBlockState(position, call.getArgument(1), 0);
return true;
}).when(fixture.world).setBlock(any(), any(), anyInt(), anyInt());
assertTrue(fixture.updates.setBlock(fixture.world, position,
Blocks.COMPOSTER.defaultBlockState(), Block.UPDATE_CLIENTS, 17));
assertArrayEquals(new long[]{16L}, fixture.marker());
}
@Test
public void denseWritesStoreOneSectionBitAndSurvivePersistentDataRoundTrip() throws Exception {
Fixture fixture = new Fixture();
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
fixture.write(new BlockPos(-32 + x, 5, 48 + z), Blocks.COMPOSTER.defaultBlockState());
}
}
assertArrayEquals(new long[]{16L}, fixture.marker());
DirtyCraftPersistentDataContainer restored = new DirtyCraftPersistentDataContainer(new CraftPersistentDataTypeRegistry());
restored.putAll(fixture.proto.persistentDataContainer.toTagCompound());
fixture.loaded.persistentDataContainer = restored;
onOwner(() -> NativeStructurePoiUpdates.reconcile(fixture.loaded));
assertEquals(256L, fixture.records().getRecords(type -> true, PoiManager.Occupancy.ANY).count());
assertFalse(restored.has(DIRTY_SECTIONS));
}
@Test
public void validSectionsReconcileFinalStatesAndKeepMatchingClaimedTickets() throws Exception {
Fixture fixture = new Fixture();
BlockPos retained = new BlockPos(-31, 5, 49);
BlockPos replaced = new BlockPos(-30, 5, 49);
BlockPos removed = new BlockPos(-29, 5, 49);
Holder<PoiType> farmer = PoiTypes.forState(Blocks.COMPOSTER.defaultBlockState()).orElseThrow();
PoiSection existing = new PoiSection.Packed(true, List.of(
new PoiRecord.Packed(retained, farmer, 0),
new PoiRecord.Packed(replaced, farmer, 1),
new PoiRecord.Packed(removed, farmer, 1))).unpack(() -> {});
fixture.pois.put(SectionPos.of(retained).asLong(), existing);
PoiRecord retainedRecord = existing.getRecords(type -> true, PoiManager.Occupancy.ANY)
.filter(record -> record.getPos().equals(retained)).findFirst().orElseThrow();
for (BlockPos position : List.of(retained, replaced, removed)) {
fixture.proto.setBlockState(position, Blocks.COMPOSTER.defaultBlockState(), 0);
}
fixture.write(replaced, Blocks.BARREL.defaultBlockState());
fixture.write(removed, Blocks.AIR.defaultBlockState());
onOwner(() -> NativeStructurePoiUpdates.reconcile(fixture.loaded));
assertEquals(PoiTypes.forState(Blocks.BARREL.defaultBlockState()), existing.getType(replaced));
assertTrue(existing.getType(removed).isEmpty());
assertSame(retainedRecord, existing.getRecords(type -> true, PoiManager.Occupancy.ANY)
.filter(record -> record.getPos().equals(retained)).findFirst().orElseThrow());
assertEquals(0, existing.getFreeTickets(retained));
onOwner(() -> NativeStructurePoiUpdates.reconcile(fixture.loaded));
assertEquals(0, existing.getFreeTickets(retained));
}
@Test
public void failedReconciliationRetainsIntentForRetry() throws Exception {
Fixture fixture = new Fixture();
fixture.write(new BlockPos(-31, 5, 49), Blocks.COMPOSTER.defaultBlockState());
IllegalStateException failure = new IllegalStateException("POI storage failure");
doThrow(failure).when(fixture.manager).add(any(), any());
onOwner(() -> assertSame(failure, assertThrows(IllegalStateException.class,
() -> NativeStructurePoiUpdates.reconcile(fixture.loaded))));
assertArrayEquals(new long[]{16L}, fixture.marker());
fixture.allowPoiAdds();
onOwner(() -> NativeStructurePoiUpdates.reconcile(fixture.loaded));
assertFalse(fixture.proto.persistentDataContainer.has(DIRTY_SECTIONS));
}
@Test
public void explicitPoiSuppressionAndReadOnlyFullChunksDoNotCreateIntent() {
Fixture fixture = new Fixture();
BlockPos position = new BlockPos(-31, 5, 49);
fixture.updates.setBlock(fixture.world, position, Blocks.COMPOSTER.defaultBlockState(),
4096, 17);
assertFalse(fixture.proto.persistentDataContainer.has(DIRTY_SECTIONS));
ImposterProtoChunk full = mock(ImposterProtoChunk.class);
when(fixture.world.getChunk(any(BlockPos.class))).thenReturn(full);
fixture.updates.setBlock(fixture.world, position, Blocks.BARREL.defaultBlockState(), 2, 17);
assertFalse(fixture.proto.persistentDataContainer.has(DIRTY_SECTIONS));
}
@Test
public void invalidSectionBitsRetainTheStoredRecord() throws Exception {
Fixture fixture = new Fixture();
fixture.proto.persistentDataContainer.set(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY, new long[]{1L << 24});
onOwner(() -> assertThrows(IllegalArgumentException.class,
() -> NativeStructurePoiUpdates.reconcile(fixture.loaded)));
assertArrayEquals(new long[]{1L << 24}, fixture.marker());
}
private static void onOwner(Runnable action) throws Exception {
FutureTask<Void> task = new FutureTask<>(action, null);
new TickThread(task, "Native POI test").start();
task.get(5L, TimeUnit.SECONDS);
}
private static PalettedContainerFactory containerFactory() {
Strategy<BlockState> blocks = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
BlockState air = Blocks.AIR.defaultBlockState();
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
Holder<Biome> biome = Holder.direct((Biome) null);
biomeIds.add(biome);
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(name -> biome, holder -> "iris-test-biome");
return new PalettedContainerFactory(blocks, air, PalettedContainer.codecRW(BlockState.CODEC, blocks, air),
biomes, biome, PalettedContainer.codecRO(biomeCodec, biomes, biome),
PalettedContainer.codecRW(biomeCodec, biomes, biome));
}
private static final class Fixture {
private final NativeStructurePoiUpdates updates = new NativeStructurePoiUpdates(4096);
private final ProtoChunk proto = new ProtoChunk(new ChunkPos(-2, 3), UpgradeData.EMPTY,
LevelHeightAccessor.create(-64, 384), containerFactory(), null);
private final LevelChunk loaded = mock(LevelChunk.class);
private final WorldGenLevel world = mock(WorldGenLevel.class);
private final PoiManager manager = mock(PoiManager.class);
private final Map<Long, PoiSection> pois = new HashMap<>();
private Fixture() {
ServerLevel level = mock(ServerLevel.class);
when(level.getPoiManager()).thenReturn(manager);
loaded.persistentDataContainer = proto.persistentDataContainer;
when(loaded.getLevel()).thenReturn(level);
when(loaded.getPos()).thenReturn(proto.getPos());
when(loaded.getSectionsCount()).thenReturn(proto.getSectionsCount());
when(loaded.getSectionYFromSectionIndex(anyInt())).thenAnswer(call -> proto.getSectionYFromSectionIndex(call.getArgument(0)));
when(loaded.getSection(anyInt())).thenAnswer(call -> proto.getSection(call.getArgument(0)));
when(loaded.getBlockState(any())).thenAnswer(call -> proto.getBlockState(call.getArgument(0)));
when(world.getChunk(any(BlockPos.class))).thenReturn(proto);
when(world.setBlock(any(), any(), anyInt(), anyInt())).thenAnswer(call -> {
proto.setBlockState(call.getArgument(0), call.getArgument(1), call.getArgument(2));
return true;
});
when(manager.getOrLoad(anyLong())).thenAnswer(call -> Optional.ofNullable(pois.get((long) call.getArgument(0))));
when(manager.getType(any())).thenAnswer(call -> {
BlockPos position = call.getArgument(0);
PoiSection section = pois.get(SectionPos.of(position).asLong());
return section == null ? Optional.empty() : section.getType(position);
});
doAnswer(call -> {
BlockPos position = call.getArgument(0);
pois.get(SectionPos.of(position).asLong()).remove(position);
return null;
}).when(manager).remove(any());
allowPoiAdds();
}
private void allowPoiAdds() {
doAnswer(call -> {
BlockPos position = call.getArgument(0);
return pois.computeIfAbsent(SectionPos.of(position).asLong(), ignored -> new PoiSection(() -> {}))
.add(position, call.getArgument(1));
}).when(manager).add(any(), any());
}
private void write(BlockPos position, BlockState state) {
updates.setBlock(world, position, state, Block.UPDATE_CLIENTS, Block.UPDATE_LIMIT);
}
private long[] marker() {
return proto.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
}
private PoiSection records() {
return pois.get(SectionPos.of(new BlockPos(-31, 5, 49)).asLong());
}
}
}
@@ -0,0 +1,464 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.GravityProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NativeStructureWorldgenAccessTest {
private static final int GENERATION_CENTER_X = 60;
private static final int GENERATION_CENTER_Z = 15;
private static final int SURFACE_FIRST_FREE_Y = 80;
private static final int FLOOR_FIRST_FREE_Y = 70;
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void ordinaryWorldFlattenPreparationHandlesReferencedVillagePiecesBeyondEightChunks() {
int farX = (GENERATION_CENTER_X - 9) << 4;
int farZ = (GENERATION_CENTER_Z + 4) << 4;
BoundingBox bounds = new BoundingBox(farX, 64, farZ, farX + 4, 90, farZ + 4);
PoolElementStructurePiece piece = new PoolElementStructurePiece(
null, StructurePoolElement.single("minecraft:empty").apply(StructureTemplatePool.Projection.RIGID),
new BlockPos(farX, 64, farZ), 1, Rotation.NONE, bounds, LiquidSettings.APPLY_WATERLOGGING);
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(
HolderSet.empty(), Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.BEARD_BOX));
StructureStart start = new StructureStart(structure, generationCenter(), 0, new PiecesContainer(List.of(piece)));
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget("minecraft:village_plains", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FLATTEN)));
BoundingBox area = new BoundingBox(generationCenter().getMinBlockX(), -64, generationCenter().getMinBlockZ(),
generationCenter().getMaxBlockX(), 319, generationCenter().getMaxBlockZ());
RecordingDelegate unbounded = new RecordingDelegate();
unbounded.rejectDistantTerrainReads = true;
assertThrows(IllegalStateException.class, () -> NativeStructurePostProcessor.prepareTerrain(
unbounded.world(), area, targets, (palette, rng, x, y, z) -> Blocks.STONE.defaultBlockState()));
RecordingDelegate recording = new RecordingDelegate();
recording.rejectDistantTerrainReads = true;
NativeStructureWorldgenAccess access = access(recording);
NativeStructurePostProcessor.prepareTerrain(
access, area, targets, (palette, rng, x, y, z) -> Blocks.STONE.defaultBlockState());
assertEquals(0, recording.heightReads);
assertEquals(0, recording.chunkReads);
assertEquals(0, recording.terrainReads);
assertEquals(0, recording.mutations);
assertEquals(91, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG,
generationCenter().getMinBlockX(), generationCenter().getMinBlockZ()));
assertEquals(1, recording.heightReads);
}
@Test
public void projectedTemplatePrefiltersUseRemoteTerrainAndRetainLocalHeightmaps() {
RecordingDelegate recording = new RecordingDelegate();
recording.rejectDistantTerrainReads = true;
NativeStructureWorldgenAccess access = access(recording);
GravityProcessor gravity = new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, -1);
BlockPos remote = new BlockPos((GENERATION_CENTER_X - 9) << 4, 64, (GENERATION_CENTER_Z + 4) << 4);
StructureTemplate.StructureBlockInfo raw = new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 4, 0), Blocks.DIRT_PATH.defaultBlockState(), null);
StructurePlaceSettings settings = new StructurePlaceSettings().addProcessor(gravity);
StructureTemplate.StructureBlockInfo projected = StructureTemplate.processBlockInfos(
access, remote, BlockPos.ZERO, settings, List.of(raw)).getFirst();
assertEquals(remote.atY(SURFACE_FIRST_FREE_Y + 3), projected.pos());
assertEquals(0, recording.heightReads);
BlockPos local = generationCenter().getMiddleBlockPosition(64);
StructureTemplate.StructureBlockInfo localBlock = new StructureTemplate.StructureBlockInfo(
BlockPos.ZERO, Blocks.DIRT_PATH.defaultBlockState(), null);
StructureTemplate.StructureBlockInfo localProjection = StructureTemplate.processBlockInfos(
access, local, BlockPos.ZERO, settings, List.of(localBlock)).getFirst();
assertEquals(local.atY(90), localProjection.pos());
assertEquals(1, recording.heightReads);
assertEquals(0, recording.chunkReads);
}
@Test
public void distanceTwoTerrainReadsNeverReachTheDelegate() {
RecordingDelegate recording = new RecordingDelegate();
NativeStructureWorldgenAccess access = access(recording);
ChunkPos generationCenter = generationCenter();
int x = generationCenter.getMiddleBlockX();
int z = (generationCenter.z() + 2) << 4;
assertSame(Blocks.STONE, access.getBlockState(new BlockPos(x, FLOOR_FIRST_FREE_Y - 1, z)).getBlock());
assertSame(Blocks.WATER, access.getBlockState(new BlockPos(x, FLOOR_FIRST_FREE_Y, z)).getBlock());
assertSame(Blocks.AIR, access.getBlockState(new BlockPos(x, SURFACE_FIRST_FREE_Y, z)).getBlock());
assertSame(Blocks.WATER.defaultBlockState().getFluidState(),
access.getFluidState(new BlockPos(x, FLOOR_FIRST_FREE_Y, z)));
assertEquals(SURFACE_FIRST_FREE_Y, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z));
assertEquals(FLOOR_FIRST_FREE_Y, access.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z));
assertNull(access.getBlockStateIfLoaded(new BlockPos(x, FLOOR_FIRST_FREE_Y, z)));
assertNull(access.getChunk(generationCenter.x(), generationCenter.z() + 2, ChunkStatus.FEATURES, false));
assertNull(access.getChunkIfLoadedImmediately(
generationCenter.x(), generationCenter.z() + 2));
List<BlockState> streamed = access.getBlockStates(new AABB(
x, FLOOR_FIRST_FREE_Y, z, x, FLOOR_FIRST_FREE_Y, z)).toList();
assertEquals(1, streamed.size());
assertSame(Blocks.WATER, streamed.getFirst().getBlock());
assertEquals(0, recording.terrainReads);
assertEquals(0, recording.heightReads);
assertEquals(0, recording.chunkReads);
}
@Test
public void distanceTwoMutationsAndSideEffectsNeverReachTheDelegate() {
RecordingDelegate recording = new RecordingDelegate();
NativeStructureWorldgenAccess access = access(recording);
ChunkPos generationCenter = generationCenter();
BlockPos position = new BlockPos(
generationCenter.getMiddleBlockX(),
FLOOR_FIRST_FREE_Y,
(generationCenter.z() + 2) << 4);
assertFalse(access.ensureCanWrite(position));
assertFalse(access.setBlock(position, Blocks.DIRT.defaultBlockState(), 2));
assertFalse(access.removeBlock(position, false));
assertFalse(access.destroyBlock(position, false));
access.updateNeighborsAt(position, Blocks.DIRT);
access.neighborShapeChanged(
Direction.NORTH, position, position.north(),
Blocks.DIRT.defaultBlockState(), 2, 512);
access.levelEvent(null, 2001, position, 0);
access.gameEvent(null, Vec3.atCenterOf(position), null);
access.addParticle(null, position.getX(), position.getY(), position.getZ(), 0, 0, 0);
access.getBlockTicks().schedule(new ScheduledTick<>(
Blocks.DIRT, position, 1L, 0L));
assertEquals(0, recording.mutations);
assertEquals(0, recording.events);
assertEquals(0, recording.blockTicks.count());
}
@Test
public void radiusOneReadsWritesAndTicksRemainUnchanged() {
RecordingDelegate recording = new RecordingDelegate();
NativeStructureWorldgenAccess access = access(recording);
ChunkPos generationCenter = generationCenter();
BlockPos position = new BlockPos(
(generationCenter.x() + 1) << 4,
FLOOR_FIRST_FREE_Y,
(generationCenter.z() - 1) << 4);
assertTrue(access.isInsideGenerationRegion(
generationCenter.x() + 1, generationCenter.z() - 1));
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
assertEquals(91, access.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, position.getX(), position.getZ()));
assertTrue(access.ensureCanWrite(position));
assertTrue(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
assertTrue(access.removeBlock(position, false));
assertTrue(access.destroyBlock(position, false));
access.getBlockTicks().schedule(new ScheduledTick<>(
Blocks.DIRT, position, 1L, 0L));
assertEquals(1, recording.terrainReads);
assertEquals(1, recording.heightReads);
assertEquals(4, recording.mutations);
assertEquals(1, recording.blockTicks.count());
}
@Test
public void virtualizedHeightsHideInRegionTerrainFromTheDelegate() {
RecordingDelegate recording = new RecordingDelegate();
ChunkPos generationCenter = generationCenter();
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
recording.world(), generationCenter,
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y,
true,
position -> false);
int x = generationCenter.getMiddleBlockX();
int z = generationCenter.getMiddleBlockZ();
assertEquals(SURFACE_FIRST_FREE_Y, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z));
assertEquals(FLOOR_FIRST_FREE_Y, access.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z));
assertEquals(0, recording.heightReads);
}
@Test
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
RecordingDelegate recording = new RecordingDelegate();
BlockPos position = generationCenter().getMiddleBlockPosition(70);
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y,
false,
position::equals);
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
assertFalse(access.ensureCanWrite(position));
assertFalse(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
assertFalse(access.removeBlock(position, false));
assertFalse(access.destroyBlock(position, false));
assertNull(access.getBlockEntity(position));
assertTrue(access.getBlockEntity(position, null).isEmpty());
access.getBlockTicks().schedule(new ScheduledTick<>(Blocks.DIRT, position, 1L, 0L));
access.updateNeighborsAt(position.east(), Blocks.DIRT);
access.neighborShapeChanged(
Direction.EAST, position, position.east(), Blocks.DIRT.defaultBlockState(), 2, 512);
assertEquals(1, recording.terrainReads);
assertEquals(0, recording.mutations);
assertEquals(0, recording.blockTicks.count());
assertTrue(access.setBlock(position.above(), Blocks.STONE.defaultBlockState(), 2));
assertEquals(1, recording.mutations);
}
@Test
public void statuslessChunkReadsPreserveWorldgenDelegateSemantics() {
RecordingDelegate recording = new RecordingDelegate();
NativeStructureWorldgenAccess access = access(recording);
ChunkPos generationCenter = generationCenter();
assertNull(access.getChunk(generationCenter.getWorldPosition()));
assertEquals(1, recording.statuslessChunkReads);
assertEquals(0, recording.statusChunkReads);
}
@Test
public void entityQueriesRejectDisjointAreasAndClampOverlaps() {
RecordingDelegate recording = new RecordingDelegate();
NativeStructureWorldgenAccess access = access(recording);
ChunkPos generationCenter = generationCenter();
int safeMaxX = generationCenter.getMaxBlockX() + 17;
int safeMinZ = generationCenter.getMinBlockZ() - 16;
int safeMaxZ = generationCenter.getMaxBlockZ() + 17;
AABB disjoint = new AABB(
safeMaxX, -64, safeMinZ,
safeMaxX + 16, 320, safeMaxZ);
AABB overlap = new AABB(
safeMaxX - 8, -128, safeMinZ - 8,
safeMaxX + 16, 400, safeMaxZ + 8);
EntityTypeTest<Entity, Entity> type = EntityTypeTest.forClass(Entity.class);
assertTrue(access.getEntities((Entity) null, disjoint, entity -> true).isEmpty());
assertTrue(access.getEntities(type, disjoint, entity -> true).isEmpty());
access.getEntities((Entity) null, overlap, entity -> true);
access.getEntities(type, overlap, entity -> true);
assertEquals(2, recording.entityAreas.size());
for (AABB delegatedArea : recording.entityAreas) {
assertEquals(safeMaxX - 8, delegatedArea.minX, 0D);
assertEquals(-64, delegatedArea.minY, 0D);
assertEquals(safeMinZ, delegatedArea.minZ, 0D);
assertEquals(safeMaxX, delegatedArea.maxX, 0D);
assertEquals(320, delegatedArea.maxY, 0D);
assertEquals(safeMaxZ, delegatedArea.maxZ, 0D);
}
}
private static NativeStructureWorldgenAccess access(RecordingDelegate recording) {
return NativeStructureWorldgenAccess.create(
recording.world(), generationCenter(),
(x, z) -> SURFACE_FIRST_FREE_Y,
(x, z) -> FLOOR_FIRST_FREE_Y,
false,
position -> false);
}
private static ChunkPos generationCenter() {
return new ChunkPos(GENERATION_CENTER_X, GENERATION_CENTER_Z);
}
private static final class RecordingDelegate implements InvocationHandler {
private final Holder<Biome> biome;
private final BiomeManager biomeManager;
private final RecordingTicks<Block> blockTicks;
private final RecordingTicks<Fluid> fluidTicks;
private final List<AABB> entityAreas;
private int terrainReads;
private int heightReads;
private int chunkReads;
private int statuslessChunkReads;
private int statusChunkReads;
private int mutations;
private int events;
private boolean rejectDistantTerrainReads;
private RecordingDelegate() {
this.biome = Holder.direct((Biome) null);
this.biomeManager = new BiomeManager((x, y, z) -> biome, 13L);
this.blockTicks = new RecordingTicks<>();
this.fluidTicks = new RecordingTicks<>();
this.entityAreas = new ArrayList<>();
}
private WorldGenLevel world() {
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(),
new Class<?>[]{WorldGenLevel.class}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) {
String name = method.getName();
if (name.equals("getSeaLevel")) {
return 63;
}
if (name.equals("getBiome")) {
return biome;
}
if (name.equals("getBiomeManager")) {
return biomeManager;
}
if (name.equals("getBlockTicks")) {
return blockTicks;
}
if (name.equals("getFluidTicks")) {
return fluidTicks;
}
if (name.equals("getMinY")) {
return -64;
}
if (name.equals("getHeight") && arguments == null) {
return 384;
}
if (name.equals("getHeight")) {
if (rejectDistantTerrainReads
&& (Math.abs(((int) arguments[1] >> 4) - GENERATION_CENTER_X) > 1
|| Math.abs(((int) arguments[2] >> 4) - GENERATION_CENTER_Z) > 1)) {
throw new IllegalStateException("Requested chunk unavailable during world generation");
}
heightReads++;
return 91;
}
if (name.equals("getBlockState")) {
terrainReads++;
return Blocks.DIRT.defaultBlockState();
}
if (name.equals("getFluidState")) {
terrainReads++;
return Blocks.WATER.defaultBlockState().getFluidState();
}
if (name.equals("getChunk")) {
chunkReads++;
if (arguments.length == 2) {
statuslessChunkReads++;
} else {
statusChunkReads++;
}
return null;
}
if (name.equals("getChunkIfLoadedImmediately")) {
chunkReads++;
return null;
}
if (name.equals("getEntities")) {
entityAreas.add((AABB) arguments[1]);
return List.of();
}
if (name.equals("ensureCanWrite") || name.equals("setBlock")
|| name.equals("removeBlock") || name.equals("destroyBlock")) {
mutations++;
return true;
}
if (name.equals("updateNeighborsAt") || name.equals("neighborShapeChanged")) {
mutations++;
return null;
}
if (name.equals("levelEvent") || name.equals("gameEvent") || name.equals("addParticle")) {
events++;
return null;
}
if (name.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (name.equals("equals")) {
return proxy == arguments[0];
}
if (name.equals("toString")) {
return "native-structure-worldgen-test-delegate";
}
throw new UnsupportedOperationException(method.toString());
}
}
private static final class RecordingTicks<T> implements LevelTickAccess<T> {
private int count;
@Override
public void schedule(ScheduledTick<T> tick) {
count++;
}
@Override
public boolean hasScheduledTick(BlockPos position, T type) {
return false;
}
@Override
public int count() {
return count;
}
@Override
public boolean willTickThisTick(BlockPos position, T type) {
return false;
}
}
}
@@ -0,0 +1,100 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.craftbukkit.CraftWorld;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class NmsChunkIOFlushTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void nativeDrainCanWaitOffOwnerUntilChunkPoiAndEntityIoFinish() throws Exception {
Fixture fixture = new Fixture();
AtomicBoolean chunkPending = new AtomicBoolean(true);
AtomicBoolean poiPending = new AtomicBoolean(true);
AtomicBoolean entityPending = new AtomicBoolean(true);
CountDownLatch chunkDrain = new CountDownLatch(1);
CountDownLatch poiDrain = new CountDownLatch(1);
CountDownLatch entityDrain = new CountDownLatch(1);
when(fixture.chunks.hasTasks()).thenAnswer(invocation -> {
chunkDrain.countDown();
return chunkPending.get();
});
when(fixture.pois.hasTasks()).thenAnswer(invocation -> {
poiDrain.countDown();
return poiPending.get();
});
when(fixture.entities.hasTasks()).thenAnswer(invocation -> {
entityDrain.countDown();
return entityPending.get();
});
ExecutorService io = Executors.newSingleThreadExecutor();
try {
Future<?> drain = io.submit(() -> fixture.binding.flushChunkIO(fixture.world));
assertTrue(chunkDrain.await(5L, TimeUnit.SECONDS));
assertFalse(drain.isDone());
chunkPending.set(false);
assertTrue(poiDrain.await(5L, TimeUnit.SECONDS));
assertFalse(drain.isDone());
poiPending.set(false);
assertTrue(entityDrain.await(5L, TimeUnit.SECONDS));
assertFalse(drain.isDone());
entityPending.set(false);
drain.get(5L, TimeUnit.SECONDS);
} finally {
chunkPending.set(false);
poiPending.set(false);
entityPending.set(false);
io.shutdownNow();
assertTrue(io.awaitTermination(5L, TimeUnit.SECONDS));
}
}
@Test
public void nativeDrainFailurePropagatesToItsCompletionReceipt() {
Fixture fixture = new Fixture();
IllegalStateException failure = new IllegalStateException("native IO failed");
when(fixture.chunks.hasTasks()).thenThrow(failure);
assertSame(failure, assertThrows(IllegalStateException.class,
() -> fixture.binding.flushChunkIO(fixture.world)));
}
private static final class Fixture {
private final NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
private final CraftWorld world = mock(CraftWorld.class);
private final ServerLevel level = mock(ServerLevel.class);
private final RegionDataController chunks = mock(RegionDataController.class);
private final RegionDataController pois = mock(RegionDataController.class);
private final RegionDataController entities = mock(RegionDataController.class);
private Fixture() {
when(world.getHandle()).thenReturn(level);
when(level.moonrise$getChunkDataController()).thenReturn(chunks);
when(level.moonrise$getPoiChunkDataController()).thenReturn(pois);
when(level.moonrise$getEntityChunkDataController()).thenReturn(entities);
}
}
}
@@ -0,0 +1,95 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.util.scheduling.FoliaScheduler;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.craftbukkit.CraftWorld;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.util.concurrent.CompletableFuture;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
public class NmsChunkTaskPollingTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void serverOwnerPumpsOnlyTheTargetWorldQueue() {
Server bukkitServer = mock(Server.class);
CraftWorld world = mock(CraftWorld.class);
ServerLevel level = mock(ServerLevel.class);
MinecraftServer server = mock(MinecraftServer.class);
ServerChunkCache chunks = mock(ServerChunkCache.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
CompletableFuture<Void> completed = new CompletableFuture<>();
when(world.getHandle()).thenReturn(level);
when(level.getServer()).thenReturn(server);
when(server.getRunningThread()).thenReturn(Thread.currentThread());
when(level.getChunkSource()).thenReturn(chunks);
when(chunks.pollTask()).thenAnswer(invocation -> completed.complete(null)).thenReturn(false);
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<FoliaScheduler> folia = mockStatic(FoliaScheduler.class)) {
bukkit.when(Bukkit::getServer).thenReturn(bukkitServer);
folia.when(() -> FoliaScheduler.isRegionizedRuntime(bukkitServer)).thenReturn(false);
assertTrue(binding.pollChunkTask(world));
assertTrue(completed.isDone());
assertFalse(binding.pollChunkTask(world));
}
verify(chunks, times(2)).pollTask();
verify(server, never()).executeAllRecentInternalTasks();
}
@Test
public void nonOwnerIsRejectedBeforeAccessingTheChunkQueue() {
Server bukkitServer = mock(Server.class);
CraftWorld world = mock(CraftWorld.class);
ServerLevel level = mock(ServerLevel.class);
MinecraftServer server = mock(MinecraftServer.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
when(world.getHandle()).thenReturn(level);
when(level.getServer()).thenReturn(server);
when(server.getRunningThread()).thenReturn(new Thread("Other server owner"));
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<FoliaScheduler> folia = mockStatic(FoliaScheduler.class)) {
bukkit.when(Bukkit::getServer).thenReturn(bukkitServer);
folia.when(() -> FoliaScheduler.isRegionizedRuntime(bukkitServer)).thenReturn(false);
assertThrows(IllegalStateException.class, () -> binding.pollChunkTask(world));
}
verify(level, never()).getChunkSource();
}
@Test
public void foliaNeverUsesTheWorldWideTaskPump() {
Server bukkitServer = mock(Server.class);
CraftWorld world = mock(CraftWorld.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<FoliaScheduler> folia = mockStatic(FoliaScheduler.class)) {
bukkit.when(Bukkit::getServer).thenReturn(bukkitServer);
folia.when(() -> FoliaScheduler.isRegionizedRuntime(bukkitServer)).thenReturn(true);
assertFalse(binding.pollChunkTask(world));
}
verifyNoInteractions(world);
}
}
@@ -0,0 +1,103 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import net.minecraft.SharedConstants;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import org.bukkit.Difficulty;
import org.bukkit.Location;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NmsEntitySpawnDifficultyTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void peacefulRejectsEveryNativeForbiddenTypeBeforeBukkitCreatesIt() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
int rejected = 0;
for (EntityType type : EntityType.values()) {
if (type.getEntityClass() == null) {
continue;
}
if (BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse(type.getKey().toString())).isAllowedInPeaceful()) {
continue;
}
assertNull(type.name(), binding.spawnEntity(location, type, CreatureSpawnEvent.SpawnReason.NATURAL));
rejected++;
}
assertTrue(rejected > 20);
verify(world, never()).spawn(any(Location.class), any(Class.class), isNull(), any(CreatureSpawnEvent.SpawnReason.class));
}
@Test
public void peacefulPreservesPassiveAndNativeMonsterCategoryExceptions() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
Entity created = mock(Entity.class);
doReturn(created).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
for (EntityType type : List.of(EntityType.COW, EntityType.VILLAGER, EntityType.WOLF, EntityType.PIGLIN, EntityType.SHULKER)) {
assertSame(type.name(), created, binding.spawnEntity(location, type, CreatureSpawnEvent.SpawnReason.NATURAL));
}
}
@Test
public void nonPeacefulDifficultiesPreserveHostileSpawns() {
CraftWorld world = mock(CraftWorld.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
Entity created = mock(Entity.class);
doReturn(created).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
for (Difficulty difficulty : List.of(Difficulty.EASY, Difficulty.NORMAL, Difficulty.HARD)) {
when(world.getDifficulty()).thenReturn(difficulty);
assertSame(created, binding.spawnEntity(location, EntityType.ZOMBIE, CreatureSpawnEvent.SpawnReason.NATURAL));
}
}
@Test
public void unrelatedBukkitSpawnFailuresStillPropagate() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
IllegalStateException failure = new IllegalStateException("Spawn rejected by platform");
doThrow(failure).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
assertSame(failure, assertThrows(IllegalStateException.class,
() -> binding.spawnEntity(location, EntityType.COW, CreatureSpawnEvent.SpawnReason.NATURAL)));
}
}
@@ -0,0 +1,114 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.history.SavedTerrainChunk;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
public class NmsSavedTerrainCheckpointTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void checkpointWaitsForDirtyAndUnloadedChunkWritesBeforeFlushAndVerification() throws Exception {
ServerLevel level = mock(ServerLevel.class);
Path root = Path.of("checkpoint-world");
byte[] receipt = new byte[]{1, 2, 3};
NmsSavedTerrainCapture.Checkpoint saved = new NmsSavedTerrainCapture.Checkpoint(new ChunkPos(4, -5),
new NmsSavedTerrainCapture.Verification("minecraft:full", receipt, 7L));
NmsSavedTerrainCapture.Checkpoint unloaded = NmsSavedTerrainCapture.checkpoint(-8, 9, null);
Set<ChunkPos> completed = new HashSet<>();
AtomicBoolean flushed = new AtomicBoolean();
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, 4, -5, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
completed.add(saved.chunk());
return Priority.COMPLETING;
});
io.when(() -> MoonriseRegionFileIO.getPriority(level, -8, 9, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
completed.add(unloaded.chunk());
return Priority.COMPLETING;
});
io.when(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
assertEquals(Set.of(saved.chunk(), unloaded.chunk()), completed);
flushed.set(true);
return null;
});
terrain.when(() -> SavedTerrainChunk.verifyCheckpoint(root, 4, -5, "minecraft:full", receipt, 7L))
.thenAnswer(invocation -> {
assertTrue(flushed.get());
return null;
});
NmsSavedTerrainCapture.finishCheckpoint(level, root, List.of(saved, unloaded));
terrain.verify(() -> SavedTerrainChunk.verifyCheckpoint(root, 4, -5, "minecraft:full", receipt, 7L));
io.verify(() -> MoonriseRegionFileIO.flush(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), never());
io.verify(() -> MoonriseRegionFileIO.getPriority(level, 100, 100, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), never());
terrain.verifyNoMoreInteractions();
}
}
@Test
public void storageFlushFailureCannotCompleteTheCheckpoint() throws Exception {
ServerLevel level = mock(ServerLevel.class);
IOException failure = new IOException("storage flush failed");
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenThrow(failure);
assertSame(failure, assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.finishCheckpoint(level, Path.of("checkpoint-world"), List.of())));
terrain.verifyNoInteractions();
}
}
@Test
public void persistedReceiptMismatchCannotCompleteTheCheckpoint() throws Exception {
ServerLevel level = mock(ServerLevel.class);
Path root = Path.of("checkpoint-world");
byte[] receipt = new byte[]{4, 5};
IOException failure = new IOException("saved terrain receipt mismatch");
NmsSavedTerrainCapture.Checkpoint saved = new NmsSavedTerrainCapture.Checkpoint(new ChunkPos(1, 2),
new NmsSavedTerrainCapture.Verification("minecraft:noise", receipt, 3L));
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, 1, 2, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenReturn(Priority.COMPLETING);
terrain.when(() -> SavedTerrainChunk.verifyCheckpoint(root, 1, 2, "minecraft:noise", receipt, 3L))
.thenThrow(failure);
assertSame(failure, assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.finishCheckpoint(level, root, List.of(saved))));
io.verify(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA));
}
}
}
@@ -0,0 +1,223 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.task.J;
import ca.spottedleaf.concurrentutil.completable.Completable;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.executor.thread.BalancedPrioritisedThreadPool;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.bukkit.World;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NmsSavedTerrainSnapshotTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void ownerReservesSaveOrderBeforeOffOwnerSerializationAndLaterAutosave() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService checkpoint = Executors.newSingleThreadExecutor();
MockedStatic<SerializableChunkData> snapshots = mockStatic(SerializableChunkData.class);
MockedStatic<PlatformHooks> hooks = mockStatic(PlatformHooks.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
Thread owner = Thread.currentThread();
ChunkAccess chunk = mock(ChunkAccess.class);
SerializableChunkData snapshot = mock(SerializableChunkData.class);
PlatformHooks platform = mock(PlatformHooks.class);
CompoundTag capturedData = new CompoundTag();
CompoundTag autosave = new CompoundTag();
List<String> submissions = new ArrayList<>();
when(chunk.getPos()).thenReturn(new ChunkPos(4, -9));
snapshots.when(() -> SerializableChunkData.copyOf(fixture.level, chunk)).thenAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
return snapshot;
});
hooks.when(PlatformHooks::get).thenReturn(platform);
doAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
return null;
}).when(platform).chunkSyncSave(fixture.level, chunk, snapshot);
when(snapshot.write()).thenAnswer(invocation -> {
assertNotSame(owner, Thread.currentThread());
assertEquals(List.of("checkpoint", "autosave"), submissions);
return capturedData;
});
io.when(() -> MoonriseRegionFileIO.scheduleSave(eq(fixture.level), eq(4), eq(-9),
any(Completable.class), any(PrioritisedExecutor.PrioritisedTask.class),
eq(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), eq(Priority.NORMAL)))
.thenAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
submissions.add("checkpoint");
return null;
});
io.when(() -> MoonriseRegionFileIO.scheduleSave(fixture.level, 4, -9, autosave,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA)).thenAnswer(invocation -> {
submissions.add("autosave");
return null;
});
NmsSavedTerrainCapture.CapturedChunk captured = NmsSavedTerrainCapture.scheduleSnapshot(fixture.level, chunk);
assertEquals(List.of("checkpoint"), submissions);
assertFalse(captured.data().isDone());
verify(snapshot, never()).write();
verify(platform).chunkSyncSave(fixture.level, chunk, snapshot);
MoonriseRegionFileIO.scheduleSave(fixture.level, 4, -9, autosave,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
Future<CompoundTag> result = checkpoint.submit(() -> NmsSavedTerrainCapture.awaitSnapshot(
fixture.level, captured, System.nanoTime() + TimeUnit.SECONDS.toNanos(3)));
assertSame(capturedData, result.get(5, TimeUnit.SECONDS));
verify(snapshot).write();
}
}
@Test
public void failedOffOwnerSerializationCannotSucceedAsACheckpoint() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService checkpoint = Executors.newSingleThreadExecutor();
MockedStatic<SerializableChunkData> snapshots = mockStatic(SerializableChunkData.class);
MockedStatic<PlatformHooks> hooks = mockStatic(PlatformHooks.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
ChunkAccess chunk = mock(ChunkAccess.class);
SerializableChunkData snapshot = mock(SerializableChunkData.class);
IllegalStateException failure = new IllegalStateException("serialization failed");
when(chunk.getPos()).thenReturn(new ChunkPos(4, -9));
snapshots.when(() -> SerializableChunkData.copyOf(fixture.level, chunk)).thenReturn(snapshot);
hooks.when(PlatformHooks::get).thenReturn(mock(PlatformHooks.class));
when(snapshot.write()).thenThrow(failure);
NmsSavedTerrainCapture.CapturedChunk captured = NmsSavedTerrainCapture.scheduleSnapshot(fixture.level, chunk);
Future<IOException> result = checkpoint.submit(() -> assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitSnapshot(fixture.level, captured,
System.nanoTime() + TimeUnit.SECONDS.toNanos(3))));
assertSame(failure, result.get(5, TimeUnit.SECONDS).getCause());
assertTrue(captured.data().isCompletedExceptionally());
}
}
@Test
public void checkpointBoundsOwnerRequestsUntilPriorNativeWritesFinish() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService owner = Executors.newSingleThreadExecutor();
ExecutorService checkpoint = Executors.newSingleThreadExecutor()) {
World world = mock(World.class);
when(world.getWorldFolder()).thenReturn(new File("checkpoint-world"));
List<NewChunkHolder> holders = new ArrayList<>(129);
for (int index = 0; index < 129; index++) {
NewChunkHolder holder = mock(NewChunkHolder.class);
setField(holder, "chunkX", index);
holders.add(holder);
}
AtomicInteger requested = new AtomicInteger();
AtomicBoolean writesBlocked = new AtomicBoolean(true);
CountDownLatch firstBatchWaiting = new CountDownLatch(1);
Future<?> result = checkpoint.submit(() -> {
try (MockedStatic<J> scheduling = mockStatic(J.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
scheduling.when(() -> J.runRegionFuture(eq(world), anyInt(), anyInt(), any(Runnable.class)))
.thenAnswer(invocation -> {
requested.incrementAndGet();
Runnable action = invocation.getArgument(3);
return CompletableFuture.runAsync(action, owner);
});
io.when(() -> MoonriseRegionFileIO.getPriority(eq(fixture.level), anyInt(), anyInt(),
eq(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))).thenAnswer(invocation -> {
firstBatchWaiting.countDown();
return writesBlocked.get() ? Priority.NORMAL : Priority.COMPLETING;
});
NmsSavedTerrainCapture.flushSnapshots(world, fixture.level, holders);
io.verify(() -> MoonriseRegionFileIO.getPriority(fixture.level, 128, 0,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), times(2));
return null;
}
});
try {
assertTrue(firstBatchWaiting.await(5, TimeUnit.SECONDS));
assertEquals(64, requested.get());
assertTrue(owner.submit(() -> true).get(5, TimeUnit.SECONDS));
assertFalse(result.isDone());
writesBlocked.set(false);
result.get(5, TimeUnit.SECONDS);
assertEquals(129, requested.get());
} finally {
writesBlocked.set(false);
}
}
}
private static void setField(Object instance, String name, Object value) throws Exception {
Field field = instance.getClass().getField(name);
field.setAccessible(true);
field.set(instance, value);
}
private static final class SaveFixture implements AutoCloseable {
private final BalancedPrioritisedThreadPool pool = new BalancedPrioritisedThreadPool(
TimeUnit.MILLISECONDS.toNanos(1), Executors.defaultThreadFactory());
private final ServerLevel level = mock(ServerLevel.class);
private SaveFixture() throws Exception {
BalancedPrioritisedThreadPool.OrderedStreamGroup group = pool.createOrderedStreamGroup();
ChunkTaskScheduler scheduler = mock(ChunkTaskScheduler.class);
setField(scheduler, "saveExecutor", group.createExecutor());
setField(scheduler, "compressionExecutor", group.createExecutor());
setField(scheduler, "chunkHolderManager", mock(ChunkHolderManager.class));
when(level.moonrise$getChunkTaskScheduler()).thenReturn(scheduler);
}
@Override
public void close() throws Exception {
pool.halt(true);
assertTrue(pool.joinInterruptable(5_000));
}
}
}
@@ -0,0 +1,135 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.concurrentutil.executor.thread.BalancedPrioritisedThreadPool;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class NmsSavedTerrainWriteBarrierTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void checkpointHelpsSaveAndCompressionWhileNativeGenerationOccupiesTheWorker() throws Exception {
BalancedPrioritisedThreadPool pool = new BalancedPrioritisedThreadPool(
TimeUnit.MILLISECONDS.toNanos(1), Executors.defaultThreadFactory());
BalancedPrioritisedThreadPool.OrderedStreamGroup group = pool.createOrderedStreamGroup();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue generation = group.createExecutor();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue saves = group.createExecutor();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue compression = group.createExecutor();
ServerLevel level = level(saves, compression);
ChunkPos chunk = new ChunkPos(4, -9);
CountDownLatch generationEntered = new CountDownLatch(1);
CountDownLatch releaseGeneration = new CountDownLatch(1);
AtomicBoolean pendingWrite = new AtomicBoolean(true);
AtomicInteger serialized = new AtomicInteger();
AtomicInteger compressed = new AtomicInteger();
AtomicReference<Thread> saveThread = new AtomicReference<>();
AtomicReference<Thread> compressionThread = new AtomicReference<>();
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, chunk.x(), chunk.z(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> pendingWrite.get() ? Priority.NORMAL : Priority.COMPLETING);
generation.queueTask(() -> {
generationEntered.countDown();
try {
assertTrue(releaseGeneration.await(5, TimeUnit.SECONDS));
} catch (InterruptedException interruption) {
Thread.currentThread().interrupt();
throw new AssertionError(interruption);
}
});
pool.adjustThreadCount(1);
assertTrue(generationEntered.await(5, TimeUnit.SECONDS));
saves.queueTask(() -> {
saveThread.set(Thread.currentThread());
serialized.incrementAndGet();
compression.queueTask(() -> {
compressionThread.set(Thread.currentThread());
compressed.incrementAndGet();
pendingWrite.set(false);
});
});
NmsSavedTerrainCapture.awaitWrite(level, chunk, System.nanoTime() + TimeUnit.SECONDS.toNanos(3));
assertEquals(1L, releaseGeneration.getCount());
assertEquals(1, serialized.get());
assertEquals(1, compressed.get());
assertFalse(pendingWrite.get());
assertSame(Thread.currentThread(), saveThread.get());
assertSame(Thread.currentThread(), compressionThread.get());
} finally {
releaseGeneration.countDown();
pool.halt(true);
assertTrue(pool.joinInterruptable(5_000));
}
}
@Test
public void waitingForNativeWriteHonorsTheSharedDeadline() throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkPos chunk = new ChunkPos(2, 3);
IOException failure = assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitWrite(level, chunk, System.nanoTime() - 1L));
assertTrue(failure.getMessage().contains("Timed out"));
}
@Test
public void interruptedNativeWriteWaitPreservesInterruption() throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkPos chunk = new ChunkPos(2, 3);
Thread.currentThread().interrupt();
try {
IOException failure = assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitWrite(level, chunk,
System.nanoTime() + TimeUnit.SECONDS.toNanos(3)));
assertTrue(failure.getMessage().contains("Interrupted"));
assertTrue(Thread.currentThread().isInterrupted());
} finally {
Thread.interrupted();
}
}
private static ServerLevel level(BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue saves,
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue compression) throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkTaskScheduler scheduler = mock(ChunkTaskScheduler.class);
Field savesField = ChunkTaskScheduler.class.getField("saveExecutor");
Field compressionField = ChunkTaskScheduler.class.getField("compressionExecutor");
savesField.setAccessible(true);
compressionField.setAccessible(true);
savesField.set(scheduler, saves);
compressionField.set(scheduler, compression);
when(level.moonrise$getChunkTaskScheduler()).thenReturn(scheduler);
return level;
}
}
@@ -0,0 +1,343 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.IdMapper;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.IglooPieces;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.IntBinaryOperator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class WorldgenTerrainHeightmapsTest {
private static final int MIN_Y = -64;
private static final int HEIGHT = 384;
private static final int LAND_TERRAIN_TOP = 70;
private static final int LAND_CANOPY_TOP = 80;
private static final int OCEAN_FLOOR_TOP = 60;
private static final int OCEAN_FLUID_TOP = 64;
private static final int LAND_X = 3;
private static final int LAND_Z = 5;
private static final int OCEAN_X = 8;
private static final int OCEAN_Z = 8;
private static final int TERRAIN_SLAB_DEPTH = 4;
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void authoringHeightmapsReadPlacedObjectsAndFluidsWithoutTerrainResolvers() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
IrisChunkGenerator.primeAuthoringHeightmaps(chunk);
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, LAND_X, LAND_Z));
assertEquals(LAND_TERRAIN_TOP + 6,
chunk.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, LAND_X, LAND_Z));
assertEquals(OCEAN_FLUID_TOP, chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, OCEAN_X, OCEAN_Z));
assertEquals(OCEAN_FLOOR_TOP, chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, OCEAN_X, OCEAN_Z));
}
@Test
public void primeTerrainWritesEngineSurfaceAndOceanFloorHeights() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertFalse(chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertFalse(chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
assertTrue(chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertTrue(chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
assertEquals(OCEAN_FLUID_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, OCEAN_X, OCEAN_Z));
assertEquals(OCEAN_FLOOR_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, OCEAN_X, OCEAN_Z));
}
@Test
public void finalHeightmapPrimingDoesNotClobberTheWorldgenHeightmaps() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
Heightmap.primeHeightmaps(chunk, ChunkStatus.FINAL_HEIGHTMAPS);
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE, LAND_X, LAND_Z));
}
@Test
public void unprimedWorldgenHeightmapResolvesTheIrisCanopyInstead() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
@Test
public void primeTerrainOverwritesAnAlreadyLazyPrimedCanopyHeight() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
@Test
public void structurePlacementPrimesTheWestNeighbourAnIglooEntranceReads() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
chunks.put(ChunkPos.pack(0, 0), terrainChunk(new ChunkPos(0, 0)));
chunks.put(ChunkPos.pack(-1, 0), terrainChunk(new ChunkPos(-1, 0)));
WorldGenLevel world = world(chunks);
StructureStart start = startInOrigin();
int canopySnap = IglooPieces.GENERATION_HEIGHT + iglooSnapOffset(world);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, new ChunkPos(0, 0), List.of(start), surfaceFirstFreeY(), floorFirstFreeY());
int terrainSnap = IglooPieces.GENERATION_HEIGHT + iglooSnapOffset(world);
assertEquals(LAND_TERRAIN_TOP + 1, world.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, -2, 5));
assertEquals(LAND_TERRAIN_TOP, terrainSnap);
assertEquals(LAND_CANOPY_TOP, canopySnap);
assertEquals(LAND_CANOPY_TOP - LAND_TERRAIN_TOP, canopySnap - terrainSnap);
}
@Test
public void structurePlacementPrimesEveryChunkTheStartFootprintTouches() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
for (int chunkX = -1; chunkX <= 1; chunkX++) {
for (int chunkZ = -1; chunkZ <= 1; chunkZ++) {
chunks.put(ChunkPos.pack(chunkX, chunkZ), terrainChunk(new ChunkPos(chunkX, chunkZ)));
}
}
WorldGenLevel world = world(chunks);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, new ChunkPos(0, 0), List.of(startInOrigin()),
surfaceFirstFreeY(), floorFirstFreeY());
for (ChunkAccess chunk : chunks.values()) {
assertTrue(chunk.getPos().toString(),
chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertTrue(chunk.getPos().toString(),
chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
}
@Test
public void structurePlacementDoesNotPrimeALoadedDistanceTwoChunk() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
ProtoChunk origin = terrainChunk(new ChunkPos(0, 0));
ProtoChunk neighbour = terrainChunk(new ChunkPos(1, 0));
ProtoChunk distanceTwo = terrainChunk(new ChunkPos(2, 0));
chunks.put(ChunkPos.pack(0, 0), origin);
chunks.put(ChunkPos.pack(1, 0), neighbour);
chunks.put(ChunkPos.pack(2, 0), distanceTwo);
WorldGenLevel world = world(chunks);
StructureStart shifted = startInOrigin();
shifted.getPieces().forEach(piece -> piece.move(16, 0, 0));
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, new ChunkPos(0, 0), List.of(shifted),
surfaceFirstFreeY(), floorFirstFreeY());
assertTrue(origin.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertTrue(neighbour.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertFalse(distanceTwo.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
}
@Test
public void structurePlacementSkipsChunksOutsideTheGenerationRegion() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
ProtoChunk origin = terrainChunk(new ChunkPos(0, 0));
chunks.put(ChunkPos.pack(0, 0), origin);
WorldGenLevel world = world(chunks);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, new ChunkPos(0, 0), List.of(startInOrigin()),
surfaceFirstFreeY(), floorFirstFreeY());
assertTrue(origin.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertEquals(LAND_TERRAIN_TOP, origin.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
private static int iglooSnapOffset(WorldGenLevel world) {
return world.getHeight(Heightmap.Types.WORLD_SURFACE_WG, -2, 5)
- IglooPieces.GENERATION_HEIGHT - 1;
}
private static StructureStart startInOrigin() {
Structure structure = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(13L), 0, 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static IntBinaryOperator surfaceFirstFreeY() {
return (x, z) -> isOcean(x, z) ? OCEAN_FLUID_TOP + 1 : LAND_TERRAIN_TOP + 1;
}
private static IntBinaryOperator floorFirstFreeY() {
return (x, z) -> isOcean(x, z) ? OCEAN_FLOOR_TOP + 1 : LAND_TERRAIN_TOP + 1;
}
private static boolean isOcean(int x, int z) {
return Math.floorMod(x, 16) == OCEAN_X && Math.floorMod(z, 16) == OCEAN_Z;
}
private static ProtoChunk terrainChunk(ChunkPos pos) {
ProtoChunk chunk = new ProtoChunk(
pos, UpgradeData.EMPTY, LevelHeightAccessor.create(MIN_Y, HEIGHT),
containerFactory(), null);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
boolean ocean = isOcean(pos.getMinBlockX() + x, pos.getMinBlockZ() + z);
int stoneTop = ocean ? OCEAN_FLOOR_TOP : LAND_TERRAIN_TOP;
for (int y = stoneTop - TERRAIN_SLAB_DEPTH; y <= stoneTop; y++) {
write(chunk, x, y, z, Blocks.STONE.defaultBlockState());
}
if (ocean) {
for (int y = OCEAN_FLOOR_TOP + 1; y <= OCEAN_FLUID_TOP; y++) {
write(chunk, x, y, z, Blocks.WATER.defaultBlockState());
}
continue;
}
for (int y = LAND_TERRAIN_TOP + 1; y <= LAND_TERRAIN_TOP + 6; y++) {
write(chunk, x, y, z, Blocks.SPRUCE_LOG.defaultBlockState());
}
for (int y = LAND_TERRAIN_TOP + 7; y <= LAND_CANOPY_TOP; y++) {
write(chunk, x, y, z, Blocks.SPRUCE_LEAVES.defaultBlockState());
}
}
}
return chunk;
}
private static void write(ProtoChunk chunk, int x, int y, int z, BlockState state) {
LevelChunkSection section = chunk.getSection(chunk.getSectionIndex(y));
section.setBlockState(x, y & 15, z, state, false);
}
private static PalettedContainerFactory containerFactory() {
Strategy<BlockState> blockStates = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
BlockState air = Blocks.AIR.defaultBlockState();
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
Holder<Biome> defaultBiome = Holder.direct((Biome) null);
biomeIds.add(defaultBiome);
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(
name -> defaultBiome, holder -> "iris-test-biome");
return new PalettedContainerFactory(
blockStates,
air,
PalettedContainer.codecRW(BlockState.CODEC, blockStates, air),
biomes,
defaultBiome,
PalettedContainer.codecRO(biomeCodec, biomes, defaultBiome),
PalettedContainer.codecRW(biomeCodec, biomes, defaultBiome));
}
private static WorldGenLevel world(Map<Long, ChunkAccess> chunks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("hasChunk")) {
return chunks.containsKey(ChunkPos.pack((int) arguments[0], (int) arguments[1]));
}
if (methodName.equals("getChunk") && arguments.length == 2) {
ChunkAccess chunk = chunks.get(ChunkPos.pack((int) arguments[0], (int) arguments[1]));
if (chunk == null) {
throw new AssertionError("Heightmap priming requested an unavailable chunk "
+ arguments[0] + "," + arguments[1]);
}
return chunk;
}
if (methodName.equals("getHeight") && arguments.length == 3) {
Heightmap.Types type = (Heightmap.Types) arguments[0];
int x = (int) arguments[1];
int z = (int) arguments[2];
ChunkAccess chunk = chunks.get(ChunkPos.pack(x >> 4, z >> 4));
if (chunk == null) {
throw new AssertionError("Heightmap read requested an unavailable chunk at "
+ x + "," + z);
}
return chunk.getHeight(type, x & 15, z & 15) + 1;
}
if (methodName.equals("getBlockState")) {
BlockPos position = (BlockPos) arguments[0];
ChunkAccess chunk = chunks.get(ChunkPos.pack(position.getX() >> 4, position.getZ() >> 4));
if (chunk == null) {
throw new AssertionError("Block read requested an unavailable chunk at " + position);
}
return chunk.getBlockState(position);
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "worldgen-heightmap-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
}
@@ -0,0 +1,294 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.structure;
import art.arcane.iris.structure.authoring.StructureCapability;
import art.arcane.iris.structure.authoring.StructureLoss;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class VillageImporterListPoolElementRuntimeContractTest {
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void pillagerOutpostTowerCompositeProducesOnePhysicalTowerChoice() throws Exception {
StructurePoolElement tower = StructurePoolElement.legacy("minecraft:pillager_outpost/watchtower")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement overgrown = StructurePoolElement.legacy("minecraft:pillager_outpost/watchtower_overgrown")
.apply(StructureTemplatePool.Projection.RIGID);
ListPoolElement towers = new ListPoolElement(
List.of(tower, overgrown),
StructureTemplatePool.Projection.RIGID
);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(towers);
Map<String, Object> entry = VillageImporter.piecePoolEntry(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower",
1
);
StructureLoss loss = VillageImporter.listElementFallbackLoss(
resolution,
"minecraft:pillager_outpost/towers"
);
Map<String, Object> basePlatePiece = VillageImporter.pieceJson(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/base_plate",
List.of(Map.of("name", "minecraft:bottom")),
0
);
Map<String, Object> watchtowerPiece = VillageImporter.pieceJson(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower",
List.of(),
1155
);
VillageImporter.PoolMemberNormalization connectorScaffold = VillageImporter.normalizePoolMember(
"minecraft:pillager_outpost/features",
"qa/pillager_outpost/pool/minecraft/pillager_outpost/features",
false,
2,
"minecraft:empty",
"minecraft:pillager_outpost/feature_plate",
"qa/pillager_outpost/piece/minecraft/pillager_outpost/feature_plate",
1,
0,
List.of(Map.of("name", "minecraft:bottom")));
assertSame(tower, resolution.physicalElement());
assertEquals("minecraft:pillager_outpost/watchtower", resolution.templateLocation());
assertEquals(1, resolution.omittedElements());
assertEquals("qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower", entry.get("piece"));
assertEquals(1, entry.get("weight"));
assertEquals(StructureCapability.LIST_ELEMENTS, loss.capability());
assertEquals("list_pool_overlays_not_imported", loss.code());
assertEquals(false, basePlatePiece.get("collidable"));
assertFalse(watchtowerPiece.containsKey("collidable"));
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, connectorScaffold.disposition());
assertTrue(connectorScaffold.losses().isEmpty());
}
@Test
public void actualWaystoneLikeAllAirMemberNormalizesToEmpty() throws Exception {
StructurePoolElement waystone = StructurePoolElement
.legacy("dungeons_and_taverns:waystones/waystone_scaffold")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(waystone);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"dungeons_and_taverns:waystones",
"qa/pool/dungeons_and_taverns/waystones",
false,
1,
"dungeons_and_taverns:waystones",
resolution.templateLocation(),
"qa/piece/dungeons_and_taverns/waystones/waystone_scaffold",
2,
0,
List.of());
assertSame(waystone, resolution.physicalElement());
assertEquals("dungeons_and_taverns:waystones/waystone_scaffold", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.EMPTY, normalization.disposition());
assertEquals(true, normalization.poolEntry().get("empty"));
assertEquals(2, normalization.poolEntry().get("weight"));
assertEquals("connectorless_all_air_member_normalized_empty",
normalization.losses().getFirst().code());
}
@Test
public void actualNonAirOrphanMemberIsOmitted() throws Exception {
StructurePoolElement orphan = StructurePoolElement
.legacy("test:orphan_house")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(orphan);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:orphan_pool",
"qa/pool/test/orphan_pool",
false,
1,
null,
resolution.templateLocation(),
"qa/piece/test/orphan_house",
4,
37,
List.of());
assertSame(orphan, resolution.physicalElement());
assertEquals("test:orphan_house", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
assertEquals("connectorless_non_air_member_omitted",
normalization.losses().getFirst().code());
assertTrue(normalization.losses().getFirst().detail().contains("37 non-air block(s)"));
assertTrue(normalization.losses().getFirst().detail().contains("no source fallback"));
assertTrue(normalization.losses().getFirst().detail().contains("selection weights"));
assertTrue(normalization.losses().getFirst().detail().contains("RNG consumption"));
}
@Test
public void actualConnectorlessStartMemberRemainsPhysical() throws Exception {
StructurePoolElement start = StructurePoolElement
.legacy("test:air_start")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(start);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:start",
"qa/pool/test/start",
true,
2,
"test:fallback",
resolution.templateLocation(),
"qa/piece/test/air_start",
6,
0,
List.of());
assertSame(start, resolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, normalization.disposition());
assertEquals("qa/piece/test/air_start", normalization.poolEntry().get("piece"));
assertTrue(normalization.losses().isEmpty());
}
@Test
public void actualMixedPoolAllAirMemberIsOmittedWithoutBecomingEmpty() throws Exception {
StructurePoolElement inert = StructurePoolElement
.legacy("test:mixed_air_inert")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(inert);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:mixed",
resolution.templateLocation(),
"qa/piece/test/mixed_air_inert",
9,
0,
List.of());
assertSame(inert, resolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
assertEquals("connectorless_all_air_mixed_member_omitted",
normalization.losses().getFirst().code());
assertTrue(normalization.losses().getFirst().detail().contains("selection weights"));
assertTrue(normalization.losses().getFirst().detail().contains("RNG consumption"));
}
@Test
public void actualMixedDistinctFallbackRetainsAllConnectorlessMembers() throws Exception {
StructurePoolElement allAirElement = StructurePoolElement
.legacy("test:mixed_all_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:mixed_non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:fallback",
allAirResolution.templateLocation(),
"qa/piece/test/mixed_all_air_primary",
9,
0,
List.of());
VillageImporter.PoolMemberNormalization nonAir = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:fallback",
nonAirResolution.templateLocation(),
"qa/piece/test/mixed_non_air_primary",
12,
41,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/mixed_all_air_primary", allAir.poolEntry().get("piece"));
assertEquals("qa/piece/test/mixed_non_air_primary", nonAir.poolEntry().get("piece"));
assertTrue(allAir.losses().isEmpty());
assertTrue(nonAir.losses().isEmpty());
}
@Test
public void actualSingletonDistinctFallbackRetainsAllAirAndNonAirMembers() throws Exception {
StructurePoolElement allAirElement = StructurePoolElement
.legacy("test:all_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:primary",
"qa/pool/test/primary",
false,
1,
"test:fallback",
allAirResolution.templateLocation(),
"qa/piece/test/all_air_primary",
5,
0,
List.of());
VillageImporter.PoolMemberNormalization nonAir = VillageImporter.normalizePoolMember(
"test:primary",
"qa/pool/test/primary",
false,
1,
"test:fallback",
nonAirResolution.templateLocation(),
"qa/piece/test/non_air_primary",
7,
29,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/all_air_primary", allAir.poolEntry().get("piece"));
assertEquals("qa/piece/test/non_air_primary", nonAir.poolEntry().get("piece"));
assertTrue(allAir.losses().isEmpty());
assertTrue(nonAir.losses().isEmpty());
}
}
+63
View File
@@ -0,0 +1,63 @@
String apiVersion = providers.gradleProperty('apiVersion').get()
def mainClass = 'art.arcane.iris.Iris'
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
.get()
dependencies {
compileOnly(project(':core'))
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
compileOnly(volmLibCoordinate) {
transitive = false
}
compileOnly(libs.paper.api)
// LogFilterSVC (constructed in Iris.enable's explicit service list) implements the
// log4j-core Filter interface, so its constructor reference needs the type resolvable.
compileOnly(libs.log4j.core)
testImplementation('junit:junit:4.13.2')
testImplementation('org.mockito:mockito-core:5.23.0')
testImplementation(libs.paper.api)
testImplementation(libs.bstats)
testImplementation(project(':core'))
testImplementation(testFixtures(project(':core')))
testImplementation(volmLibCoordinate) {
transitive = false
}
compileOnly(libs.placeholderApi)
testImplementation(libs.placeholderApi) {
transitive = false
}
}
tasks.named('processResources').configure {
def pluginProperties = [
name : rootProject.name,
version : rootProject.version,
apiVersion: apiVersion,
main : mainClass,
bootstrapper: bootstrapperClass,
]
inputs.properties(pluginProperties)
filesMatching(['**/paper-plugin.yml', '**/plugin.yml']) {
expand(pluginProperties)
}
}
tasks.named('compileJava', JavaCompile).configure {
// lombok.config decides how equals/hashCode/toString are generated (fields, never getters), so editing it
// changes this module's bytecode. Gradle does not know that on its own and would serve a stale up-to-date
// build until something else in the module changed.
inputs.file(rootProject.file('lombok.config'))
.withPropertyName('lombokConfig')
.withPathSensitivity(PathSensitivity.NONE)
}
tasks.named('jar', Jar).configure {
archiveBaseName.set('iris-bukkit-plugin')
}
tasks.named('test').configure {
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,196 @@
package art.arcane.iris;
import art.arcane.iris.world.lifecycle.BukkitStartupPaths;
import art.arcane.iris.world.lifecycle.BukkitWorldConfiguration;
import art.arcane.iris.world.lifecycle.BukkitWorldConfiguration.IrisWorldStorageEntry;
import art.arcane.iris.world.lifecycle.HuskWorldQuarantine;
import art.arcane.iris.world.lifecycle.MissingWorldStorageLog;
import art.arcane.iris.world.lifecycle.WorldReplacementBootstrap;
import art.arcane.iris.world.lifecycle.WorldReplacementBootstrapMarker;
import art.arcane.iris.pack.DefaultPackBootstrapProvisioner;
import art.arcane.iris.pack.DefaultPackBootstrapProvisioner.ProvisionResult;
import art.arcane.iris.platform.bootstrap.SlimJar;
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
@SuppressWarnings("UnstableApiUsage")
public final class IrisBootstrap implements PluginBootstrap {
@Override
public void bootstrap(BootstrapContext context) {
WorldReplacementBootstrapMarker.markBootstrapped();
try {
loadRuntimeLibraries(context);
BukkitStartupPaths startupPaths = BukkitStartupPaths.resolveCurrent();
reconcilePendingWorldReplacements(context, startupPaths);
quarantineWorthlessHusks(startupPaths, message -> context.getLogger().warn(message));
requireUsableWorldStorage(startupPaths);
ProvisionResult provisioned = provision(context, startupPaths);
Path datapackRoot = provisioned.datapackRoot();
context.getLogger().info("Iris startup datapack is {} at {}", provisioned.status(), datapackRoot);
} catch (Throwable failure) {
armStartupFailure(context, failure);
}
}
private static void loadRuntimeLibraries(BootstrapContext context) {
SlimJar.loadBootstrap(
context.getDataDirectory().resolve("cache").resolve("libraries"),
new SlimJar.BootstrapLogger() {
@Override
public void info(String message) {
context.getLogger().info(message);
}
@Override
public void error(String message) {
context.getLogger().error(message);
}
@Override
public void debug(String message) {
context.getLogger().debug(message);
}
});
}
private static void reconcilePendingWorldReplacements(
BootstrapContext context,
BukkitStartupPaths startupPaths
) {
try {
WorldReplacementBootstrap.ReconcileResult result = WorldReplacementBootstrap.reconcile(
context.getDataDirectory(),
startupPaths.levelRoot(),
startupPaths.bukkitConfiguration(),
message -> context.getLogger().info(message)
);
if (result.transactions() > 0) {
context.getLogger().info(
"Reconciled {} pending Iris world replacement(s): {} published, {} rolled back, {} retained",
result.transactions(),
result.published(),
result.rolledBack(),
result.retained()
);
}
} catch (IOException failure) {
throw new IllegalStateException(
"Unable to reconcile pending Iris world replacements before registry bootstrap",
failure
);
}
}
/**
* Moves a hot-deleted world's husk out of {@code <levelRoot>/dimensions/iris} before anything enumerates
* it.
* <p>
* Deleting a loaded world's folder and letting the server save the level back leaves a directory holding
* only the server's own {@code data/} skeleton. Left there it stops the boot whichever way it is
* classified: as unusable storage it trips the guard below, and as an empty folder it is excluded from
* the dimension registry, which trips Paper's interactive world-migration gate - a prompt that consumes
* no console input and hangs startup forever. The folder is moved rather than deleted, so it stays
* recoverable; {@link HuskWorldQuarantine} refuses anything that is not provably worthless.
*/
static void quarantineWorthlessHusks(BukkitStartupPaths startupPaths, Consumer<String> warn) {
HuskWorldQuarantine.quarantineWorthlessHusks(startupPaths.levelRoot(), warn);
}
/**
* Stops startup when a world the server is about to load carries Iris storage Iris cannot generate from.
* <p>
* The server enumerates levels from {@code <levelRoot>/dimensions/<namespace>/<key>} on disk, so a world
* whose folder is present is going to be loaded whatever Iris does; and CraftBukkit swallows whatever
* {@code getDefaultWorldGenerator} throws and falls back to the vanilla generator, which writes vanilla
* terrain straight into that world's region files. A pack snapshot that has gone missing therefore has
* to be found here, before any level is created, exactly as a corrupt {@code dimensions/<id>.json} is.
* <p>
* A world with no folder at all is not a startup failure: nothing enumerates it, nothing loads it, and
* its bukkit.yml and Multiverse entries are what make restoring the folder a complete recovery. Neither
* is a folder that holds no pack snapshot and no world data - {@link #quarantineWorthlessHusks} has
* already moved those out of the dimensions tree, and one that survives it (a Spigot-layout world folder,
* or a move that failed) owns nothing a vanilla generator could destroy. Both are only reported.
*/
static void requireUsableWorldStorage(BukkitStartupPaths startupPaths) throws IOException {
Path levelRoot = startupPaths.levelRoot();
Path levelName = levelRoot.getFileName();
if (levelName == null || levelName.toString().isBlank()) {
throw new IOException("Configured level root has no startup level id: " + levelRoot);
}
List<IrisWorldStorageEntry> entries = BukkitWorldConfiguration.auditIrisWorldStorage(
startupPaths.bukkitConfiguration().toFile(),
levelName.toString(),
levelRoot
);
StringBuilder unusable = new StringBuilder();
for (IrisWorldStorageEntry entry : entries) {
switch (entry.state()) {
case PRESENT -> {
}
case MISSING -> MissingWorldStorageLog.warnOnce(
entry.configuredWorldName(),
entry.storagePath());
case EMPTY -> MissingWorldStorageLog.warnEmptyOnce(
entry.configuredWorldName(),
entry.storagePath());
case UNUSABLE -> unusable.append(unusable.isEmpty() ? "" : "; ")
.append(entry.configuredWorldName())
.append(" (")
.append(entry.storagePath())
.append(": ")
.append(entry.detail() == null ? "storage is unusable" : entry.detail())
.append(')');
}
}
if (unusable.isEmpty()) {
return;
}
throw new IllegalStateException("Iris world storage is unusable and the server would generate vanilla"
+ " terrain over it: " + unusable
+ ". Restore each world folder from a backup, or move it aside; a world with no folder is"
+ " reported and skipped, and the server starts.");
}
static void armStartupFailure(BootstrapContext context, Throwable failure) {
armStartupFailure(context, failure, LifecycleEvents.DATAPACK_DISCOVERY);
}
static <E extends LifecycleEvent> void armStartupFailure(
BootstrapContext context,
Throwable failure,
LifecycleEventType<? super BootstrapContext, ? extends E, ?> eventType
) {
context.getLifecycleManager().registerEventHandler(eventType, event -> {
throw new IllegalStateException("Iris bootstrap did not establish safe world-generation state.", failure);
});
try {
context.getLogger().error(
"Iris bootstrap failed; registry and world startup will be stopped at datapack discovery.",
failure
);
} catch (Throwable loggingFailure) {
failure.addSuppressed(loggingFailure);
failure.printStackTrace(System.err);
}
}
private static ProvisionResult provision(BootstrapContext context, BukkitStartupPaths startupPaths) {
try {
return DefaultPackBootstrapProvisioner.provision(
context.getDataDirectory(),
message -> context.getLogger().info(message),
startupPaths
);
} catch (IOException e) {
throw new IllegalStateException("Unable to provision the Iris startup datapack", e);
}
}
}
@@ -0,0 +1,69 @@
package art.arcane.iris;
import art.arcane.iris.platform.bootstrap.SlimJar;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.loader.library.impl.JarLibrary;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
@SuppressWarnings("UnstableApiUsage")
public final class IrisPluginLoader implements PluginLoader {
@Override
public void classloader(PluginClasspathBuilder classpathBuilder) {
PluginProviderContext context = classpathBuilder.getContext();
Path libraryRoot = context.getDataDirectory().resolve("cache").resolve("libraries");
SlimJar.loadBootstrap(libraryRoot, new SlimJar.BootstrapLogger() {
@Override
public void info(String message) {
context.getLogger().info(message);
}
@Override
public void error(String message) {
context.getLogger().error(message);
}
@Override
public void debug(String message) {
context.getLogger().debug(message);
}
});
for (Path library : relocatedLibraries(libraryRoot)) {
classpathBuilder.addLibrary(new JarLibrary(library));
}
}
static List<Path> relocatedLibraries(Path libraryRoot) {
try (Stream<Path> paths = Files.walk(libraryRoot)) {
List<Path> libraries = paths
.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().endsWith(".jar"))
.filter(IrisPluginLoader::isRelocatedLibrary)
.sorted()
.toList();
if (libraries.isEmpty()) {
throw new IllegalStateException("Iris runtime library provisioning produced no relocated libraries.");
}
return libraries;
} catch (IOException failure) {
throw new IllegalStateException("Unable to enumerate provisioned Iris runtime libraries.", failure);
}
}
private static boolean isRelocatedLibrary(Path path) {
int count = path.getNameCount();
for (int i = 0; i < count - 2; i++) {
if ("relocated".equals(path.getName(i).toString())
&& "Iris".equals(path.getName(i + 1).toString())) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,11 @@
package art.arcane.iris.api.pregen;
public enum IrisPregenPhase {
STARTED,
TICK,
PAUSED,
RESUMED,
SAVING,
COMPLETED,
CANCELLED
}
@@ -0,0 +1,31 @@
package art.arcane.iris.api.pregen;
import java.util.Objects;
public record IrisPregenProgress(
String worldName,
String worldIdentity,
double percent,
long generatedChunks,
long totalChunks,
long remainingChunks,
long failedChunks,
double chunksPerSecond,
long etaMillis,
long elapsedMillis,
String method,
boolean paused) {
public IrisPregenProgress {
Objects.requireNonNull(worldIdentity, "worldIdentity");
worldName = worldName == null ? worldIdentity : worldName;
method = method == null ? "" : method;
percent = Double.isFinite(percent) ? Math.clamp(percent, 0D, 100D) : 0D;
generatedChunks = Math.max(0L, generatedChunks);
totalChunks = Math.max(0L, totalChunks);
remainingChunks = Math.max(0L, remainingChunks);
failedChunks = Math.max(0L, failedChunks);
chunksPerSecond = Double.isFinite(chunksPerSecond) ? Math.max(0D, chunksPerSecond) : 0D;
etaMillis = Math.max(0L, etaMillis);
elapsedMillis = Math.max(0L, elapsedMillis);
}
}
@@ -0,0 +1,35 @@
package art.arcane.iris.api.pregen;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.Objects;
public class IrisPregenerationEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final IrisPregenPhase phase;
private final IrisPregenProgress progress;
public IrisPregenerationEvent(IrisPregenPhase phase, IrisPregenProgress progress) {
this.phase = Objects.requireNonNull(phase, "phase");
this.progress = Objects.requireNonNull(progress, "progress");
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
public IrisPregenPhase getPhase() {
return phase;
}
public IrisPregenProgress getProgress() {
return progress;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
}
@@ -0,0 +1,24 @@
package art.arcane.iris.api.terrain;
import java.util.List;
public record IrisBiomeInfo(
String key,
String name,
String regionKey,
String regionName,
String derivativeKey,
String vanillaDerivativeKey,
String type,
List<IrisCustomBiomeInfo> customDerivatives) {
public IrisBiomeInfo {
key = key == null ? "" : key;
name = name == null ? "" : name;
regionKey = regionKey == null ? "" : regionKey;
regionName = regionName == null ? "" : regionName;
derivativeKey = derivativeKey == null ? "" : derivativeKey;
vanillaDerivativeKey = vanillaDerivativeKey == null ? "" : vanillaDerivativeKey;
type = type == null ? "" : type;
customDerivatives = List.copyOf(customDerivatives);
}
}
@@ -0,0 +1,12 @@
package art.arcane.iris.api.terrain;
public enum IrisColumnField {
SURFACE_HEIGHT,
NATURAL_HEIGHT,
SURFACE_KIND,
BIOME_KEY,
RIVER_STATE,
RIVER_DISTANCE,
RIVER_FLOW,
RIVER_WATER_SURFACE_Y
}
@@ -0,0 +1,61 @@
package art.arcane.iris.api.terrain;
import java.util.EnumSet;
import java.util.Objects;
public record IrisColumnQuery(
int minBlockX,
int minBlockZ,
int maxBlockX,
int maxBlockZ,
int strideBlocks,
EnumSet<IrisColumnField> fields) {
public IrisColumnQuery {
Objects.requireNonNull(fields, "fields");
if (fields.isEmpty()) {
throw new IllegalArgumentException("at least one field is required");
}
if (maxBlockX < minBlockX || maxBlockZ < minBlockZ) {
throw new IllegalArgumentException("query bounds are inverted");
}
if (strideBlocks < 1) {
throw new IllegalArgumentException("strideBlocks must be at least 1");
}
fields = EnumSet.copyOf(fields);
}
public static IrisColumnQuery rect(
int minBlockX,
int minBlockZ,
int maxBlockX,
int maxBlockZ,
int strideBlocks,
EnumSet<IrisColumnField> fields) {
return new IrisColumnQuery(minBlockX, minBlockZ, maxBlockX, maxBlockZ, strideBlocks, fields);
}
public long columnCount() {
long columnsX = (((long) maxBlockX - (long) minBlockX) / strideBlocks) + 1L;
long columnsZ = (((long) maxBlockZ - (long) minBlockZ) / strideBlocks) + 1L;
return saturatedProduct(columnsX, columnsZ);
}
public long chunkCount() {
long chunksX = ((long) (maxBlockX >> 4) - (long) (minBlockX >> 4)) + 1L;
long chunksZ = ((long) (maxBlockZ >> 4) - (long) (minBlockZ >> 4)) + 1L;
return saturatedProduct(chunksX, chunksZ);
}
private static long saturatedProduct(long left, long right) {
try {
return Math.multiplyExact(left, right);
} catch (ArithmeticException overflow) {
return Long.MAX_VALUE;
}
}
@Override
public EnumSet<IrisColumnField> fields() {
return EnumSet.copyOf(fields);
}
}
@@ -0,0 +1,64 @@
package art.arcane.iris.api.terrain;
import java.util.Objects;
public record IrisColumnSample(
int blockX,
int blockZ,
int surfaceHeight,
int naturalHeight,
IrisSurfaceKind surfaceKind,
String biomeKey,
IrisRiverState riverState,
double riverDistance,
int riverFlow,
int riverWaterSurfaceY
) {
public static final int UNAVAILABLE_HEIGHT = Integer.MIN_VALUE;
public static final double UNAVAILABLE_RIVER_DISTANCE = Double.NaN;
public static final int UNAVAILABLE_RIVER_FLOW = -1;
public IrisColumnSample {
Objects.requireNonNull(surfaceKind, "surfaceKind");
Objects.requireNonNull(riverState, "riverState");
biomeKey = biomeKey == null || biomeKey.isBlank() ? null : biomeKey;
if (!Double.isNaN(riverDistance) && (!Double.isFinite(riverDistance) || riverDistance < 0D)) {
throw new IllegalArgumentException("riverDistance must be non-negative, finite, or unavailable");
}
if (riverFlow < UNAVAILABLE_RIVER_FLOW) {
throw new IllegalArgumentException("riverFlow must be non-negative or unavailable");
}
}
public boolean hasSurfaceHeight() {
return surfaceHeight != UNAVAILABLE_HEIGHT;
}
public boolean hasNaturalHeight() {
return naturalHeight != UNAVAILABLE_HEIGHT;
}
public boolean hasSurfaceKind() {
return surfaceKind != IrisSurfaceKind.UNKNOWN;
}
public boolean hasBiomeKey() {
return biomeKey != null;
}
public boolean hasRiverState() {
return riverState != IrisRiverState.NONE;
}
public boolean hasRiverDistance() {
return !Double.isNaN(riverDistance);
}
public boolean hasRiverFlow() {
return riverFlow != UNAVAILABLE_RIVER_FLOW;
}
public boolean hasRiverWaterSurfaceY() {
return riverWaterSurfaceY != UNAVAILABLE_HEIGHT;
}
}
@@ -0,0 +1,6 @@
package art.arcane.iris.api.terrain;
@FunctionalInterface
public interface IrisColumnSink {
void accept(IrisColumnSample sample);
}
@@ -0,0 +1,8 @@
package art.arcane.iris.api.terrain;
public record IrisCustomBiomeInfo(String id, String registryKey) {
public IrisCustomBiomeInfo {
id = id == null ? "" : id;
registryKey = registryKey == null ? "" : registryKey;
}
}
@@ -0,0 +1,7 @@
package art.arcane.iris.api.terrain;
public enum IrisRiverState {
NONE,
WET,
DRY
}
@@ -0,0 +1,12 @@
package art.arcane.iris.api.terrain;
public enum IrisSurfaceKind {
UNKNOWN,
LAND,
SHORE,
RIVER,
RIVER_SHORE,
DRY_CHANNEL,
OCEAN,
VOID
}
@@ -0,0 +1,34 @@
package art.arcane.iris.api.terrain;
import org.bukkit.World;
import java.util.Optional;
import java.util.OptionalInt;
public interface IrisTerrainService {
boolean isIrisWorld(World world);
Optional<IrisWorldInfo> worldInfo(World world);
OptionalInt surfaceHeight(World world, int blockX, int blockZ);
IrisSurfaceKind surfaceKind(World world, int blockX, int blockZ);
Optional<String> surfaceBiomeKey(World world, int blockX, int blockZ);
Optional<String> surfaceBiomeName(World world, int blockX, int blockZ);
Optional<IrisBiomeInfo> surfaceBiomeInfo(World world, int blockX, int blockZ);
Optional<String> biomeKey(World world, int blockX, int blockY, int blockZ);
Optional<String> regionKey(World world, int blockX, int blockZ);
Optional<String> regionName(World world, int blockX, int blockZ);
int maxSampleColumns();
int maxSampleChunks();
boolean sampleColumns(World world, IrisColumnQuery query, IrisColumnSink sink);
}
@@ -0,0 +1,24 @@
package art.arcane.iris.api.terrain;
import java.util.Objects;
public record IrisWorldInfo(
String dimensionKey,
String worldIdentity,
long seed,
int minHeight,
int maxHeight,
int fluidHeight,
boolean studio) {
public IrisWorldInfo {
Objects.requireNonNull(dimensionKey, "dimensionKey");
Objects.requireNonNull(worldIdentity, "worldIdentity");
if (maxHeight <= minHeight) {
throw new IllegalArgumentException("maxHeight must exceed minHeight");
}
}
public int height() {
return maxHeight - minHeight;
}
}
@@ -0,0 +1,12 @@
package art.arcane.iris.api.tree;
import org.bukkit.block.Block;
import org.bukkit.event.block.BlockBreakEvent;
public interface IrisTreeFellerService {
boolean tryFell(BlockBreakEvent event, TreeFellerOptions options);
boolean isManagedBreak(BlockBreakEvent event);
boolean isTreeBlock(Block block);
}
@@ -0,0 +1,6 @@
package art.arcane.iris.api.tree;
public enum TreeFellerAccess {
STANDALONE,
INTEGRATION_OVERRIDE
}
@@ -0,0 +1,32 @@
package art.arcane.iris.api.tree;
import java.util.Objects;
public record TreeFellerOptions(
TreeFellerAccess access,
int durabilityPreservationChance,
TreeFellerRunHooks runHooks
) {
public TreeFellerOptions {
Objects.requireNonNull(access, "access");
Objects.requireNonNull(runHooks, "runHooks");
if (durabilityPreservationChance < 0 || durabilityPreservationChance > 100) {
throw new IllegalArgumentException("durabilityPreservationChance must be between 0 and 100");
}
}
public static TreeFellerOptions standalone() {
return new TreeFellerOptions(TreeFellerAccess.STANDALONE, 0, TreeFellerRunHooks.NONE);
}
public static TreeFellerOptions integrationOverride(
int durabilityPreservationChance,
TreeFellerRunHooks runHooks
) {
return new TreeFellerOptions(
TreeFellerAccess.INTEGRATION_OVERRIDE,
durabilityPreservationChance,
runHooks
);
}
}
@@ -0,0 +1,30 @@
package art.arcane.iris.api.tree;
public interface TreeFellerRunHooks {
TreeFellerRunHooks NONE = new TreeFellerRunHooks() {
@Override
public void onActivationAccepted() {
}
@Override
public boolean reserveLogCost() {
return true;
}
@Override
public void commitLogCost() {
}
@Override
public void refundLogCost() {
}
};
void onActivationAccepted();
boolean reserveLogCost();
void commitLogCost();
void refundLogCost();
}
@@ -0,0 +1,44 @@
package art.arcane.iris.api.world;
import art.arcane.iris.api.terrain.IrisWorldInfo;
import org.bukkit.World;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.Objects;
import java.util.Optional;
public class IrisWorldEngineEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final World world;
private final IrisWorldPhase phase;
private final IrisWorldInfo info;
public IrisWorldEngineEvent(World world, IrisWorldPhase phase, IrisWorldInfo info) {
this.world = Objects.requireNonNull(world, "world");
this.phase = Objects.requireNonNull(phase, "phase");
this.info = info;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
public World getWorld() {
return world;
}
public IrisWorldPhase getPhase() {
return phase;
}
public Optional<IrisWorldInfo> getInfo() {
return Optional.ofNullable(info);
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
}
@@ -0,0 +1,7 @@
package art.arcane.iris.api.world;
public enum IrisWorldPhase {
ENGINE_READY,
ENGINE_HOTLOADED,
ENGINE_CLOSING
}
@@ -0,0 +1,74 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.pack.datapack.DatapackIngestService;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import java.util.List;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
@Director(name = "datapack", aliases = {"datapacks", "dp"}, description = "Download & manage external datapack imports", descriptionKey = "iris.director.commanddatapack.director.download_manage_external_datapack_imports_modrinth")
public class CommandDatapack implements DirectorExecutor {
@Director(description = "Download/update every datapack listed in a pack dimension's 'datapackImports' and install it into the world so its structures register like vanilla", descriptionKey = "iris.director.commanddatapack.director.download_update_every_datapack_listed_pack_dimension_s_datapackimports_install_it_into", aliases = {"pull"})
public void ingest(
@Param(description = "Restart the server when new datapacks are installed (required for new structures to register and generate)", descriptionKey = "iris.director.commanddatapack.param.restart_server_when_new_datapacks_are_installed_required_new_structures_register_generate", defaultValue = "false")
boolean restart
) {
VolmitSender sender = sender();
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_STARTING_DATAPACK_INGEST));
J.a(() -> DatapackIngestService.ingestAll(sender, restart));
}
@Director(description = "List configured datapack imports and their installed versions", descriptionKey = "iris.director.commanddatapack.director.list_configured_datapack_imports_their_installed_versions", aliases = {"ls"})
public void list() {
VolmitSender sender = sender();
KList<String> configured = DatapackIngestService.collectConfiguredImports();
List<DatapackIngestService.Entry> installed = DatapackIngestService.installed();
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_CONFIGURED_DATAPACK_IMPORTS, MessageArgument.untrusted("value", configured.size())));
for (String url : configured) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_MESSAGE, MessageArgument.untrusted("url", url)));
}
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_INSTALLED_DATAPACKS, MessageArgument.untrusted("value", installed.size())));
for (DatapackIngestService.Entry entry : installed) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_MESSAGE_2, MessageArgument.untrusted("value", entry.id), MessageArgument.untrusted("value2", (entry.versionNumber == null ? "?" : entry.versionNumber))));
}
if (configured.isEmpty()) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DATAPACK_ADD_MODRINTH_URLS_DIMENSION_S_DATAPACKIMPORTS_LIST_THEN_RUN_IRIS));
}
}
@Director(description = "Remove an installed datapack by id (also delete its URL from datapackImports to keep it gone)", descriptionKey = "iris.director.commanddatapack.director.remove_installed_datapack_by_id_also_delete_its_url_from_datapackimports_keep", aliases = {"rm"})
public void remove(
@Param(description = "The datapack id (folder name) shown by /iris datapack list", descriptionKey = "iris.director.commanddatapack.param.datapack_id_folder_name_shown_by_iris_datapack_list")
String id
) {
DatapackIngestService.remove(sender(), id);
}
}
@@ -0,0 +1,39 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.command.DirectorExecutor;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.director.help.DirectorMiniMenu;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.plugin.ComponentMessenger;
import org.bukkit.command.CommandSender;
@Director(name = "debug", description = "Iris diagnostic tools", descriptionKey = "iris.director.commanddebug.description")
public final class CommandDebug implements DirectorExecutor {
@Director(name = "dump", sync = true, description = "Create and optionally upload a diagnostic report", descriptionKey = "iris.director.commandiris.director.debugdump")
public void dump(
@Param(name = "upload", defaultValue = "true", description = "Upload the report to mclo.gs", descriptionKey = "iris.director.commandiris.param.debugdump_upload") boolean upload,
@Param(name = "sender", contextual = true) CommandSender sender
) {
Iris.instance.debugDump().request(sender, upload);
}
@Director(sync = true, description = "Print version information", descriptionKey = "iris.director.commandiris.director.print_version_information")
public void version(@Param(name = "sender", contextual = true) CommandSender sender) {
ComponentMessenger.sendMarkup(sender, DirectorMiniMenu.version(
"Iris", Iris.instance.getDescription().getVersion(), DirectorMiniMenu.Theme.irisGreen()));
}
@Director(description = "Toggle debug", descriptionKey = "iris.director.commandiris.director.toggle_debug")
public void toggle() {
boolean enabled = !IrisSettings.get().getGeneral().isDebug();
IrisSettings.get().getGeneral().setDebug(enabled);
IrisSettings.get().forceSave();
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_SET_DEBUG,
MessageArgument.untrusted("to", enabled)));
}
}
@@ -0,0 +1,419 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import com.google.gson.JsonObject;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.datapack.ServerConfigurator;
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.platform.bukkit.nms.datapack.DataVersion;
import art.arcane.iris.world.runtime.ChunkClearer;
import art.arcane.iris.generation.validation.GoldenHashScanner;
import art.arcane.iris.world.runtime.InPlaceChunkRegenerator;
import art.arcane.iris.generation.runtime.IrisEngineSVC;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.pack.IrisPackBenchmarking;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.iris.generation.runtime.IrisEngineMantle;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.platform.generation.PlatformChunkGenerator;
import art.arcane.iris.world.storage.matter.IrisMatterContext;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.localization.C;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.io.CountingDataInputStream;
import art.arcane.volmlib.util.mantle.runtime.TectonicPlate;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.iris.world.storage.region.MCAFile;
import art.arcane.iris.world.storage.region.MCAUtil;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.BukkitRuntimeMessages;
@Director(name = "Developer", origin = DirectorOrigin.BOTH, description = "Iris World Manager", descriptionKey = "iris.director.commanddeveloper.director.iris_world_manager", aliases = {"dev"})
public class CommandDeveloper implements DirectorExecutor {
@Director(description = "Get Loaded TectonicPlates Count", descriptionKey = "iris.director.commanddeveloper.director.get_loaded_tectonicplates_count", origin = DirectorOrigin.BOTH, sync = true)
public void EngineStatus() {
Iris.service(IrisEngineSVC.class)
.engineStatus(sender());
}
@Director(description = "Hash generated block output of a fixed area for determinism/identity testing", descriptionKey = "iris.director.commanddeveloper.director.hash_generated_block_output_fixed_area_determinism_identity_testing", origin = DirectorOrigin.BOTH)
public void genhash(
@Param(description = "The world to hash", descriptionKey = "iris.director.commanddeveloper.param.world_hash", contextual = true, contextualOverride = true)
World world,
@Param(description = "Radius in chunks around the center", descriptionKey = "iris.director.commanddeveloper.param.radius_chunks_around_center", defaultValue = "4")
int radius,
@Param(description = "Center chunk X", descriptionKey = "iris.director.commanddeveloper.param.center_chunk_x", defaultValue = "0")
int centerX,
@Param(description = "Center chunk Z", descriptionKey = "iris.director.commanddeveloper.param.center_chunk_z", defaultValue = "0")
int centerZ) {
if (world == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_WORLD_IS_NULL));
return;
}
VolmitSender sender = sender();
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DEVELOPER_GENHASH_STARTED_CHUNKS, MessageArgument.untrusted("value", ((radius * 2 + 1) * (radius * 2 + 1)))));
J.a(() -> runGenhash(sender, world, radius, centerX, centerZ));
}
private void runGenhash(VolmitSender sender, World world, int radius, int centerX, int centerZ) {
long startMs = M.ms();
int minY = world.getMinHeight();
int maxY = world.getMaxHeight();
long globalHash = 0L;
long solidBlocks = 0L;
Map<String, Long> histogram = new TreeMap<>();
JsonObject chunkHashes = new JsonObject();
for (int rx = centerX - radius; rx <= centerX + radius; rx++) {
for (int rz = centerZ - radius; rz <= centerZ + radius; rz++) {
org.bukkit.ChunkSnapshot snapshot;
try {
org.bukkit.Chunk loaded = art.arcane.iris.platform.bukkit.BukkitPlatform.chunkAtAsync(world, rx, rz, true).get();
snapshot = loaded.getChunkSnapshot(false, false, false);
} catch (Throwable e) {
Iris.reportError(e);
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DEVELOPER_GENHASH_FAILED_AT_CHUNK, MessageArgument.untrusted("rx", rx), MessageArgument.untrusted("rz", rz), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
return;
}
long chunkHash = 0L;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int worldX = (rx << 4) + x;
int worldZ = (rz << 4) + z;
for (int y = minY; y < maxY; y++) {
org.bukkit.Material material = snapshot.getBlockType(x, y, z);
long positionSeed = ((long) worldX * 0x9E3779B97F4A7C15L)
^ ((long) y * 0xC2B2AE3D27D4EB4FL)
^ ((long) worldZ * 0x165667B19E3779F9L);
long blockHash = genHashMix(positionSeed ^ ((long) (material.ordinal() + 1) * 0xD6E8FEB86659FD93L));
chunkHash ^= blockHash;
if (material != org.bukkit.Material.AIR
&& material != org.bukkit.Material.CAVE_AIR
&& material != org.bukkit.Material.VOID_AIR) {
histogram.merge(material.name(), 1L, Long::sum);
solidBlocks++;
}
}
}
}
globalHash ^= chunkHash;
chunkHashes.addProperty(rx + "," + rz, Long.toHexString(chunkHash));
}
}
int side = radius * 2 + 1;
JsonObject result = new JsonObject();
result.addProperty("global", Long.toHexString(globalHash));
result.addProperty("chunks", side * side);
result.addProperty("solidBlocks", solidBlocks);
result.addProperty("minY", minY);
result.addProperty("maxY", maxY);
JsonObject hist = new JsonObject();
for (Map.Entry<String, Long> entry : histogram.entrySet()) {
hist.addProperty(entry.getKey(), entry.getValue());
}
result.add("histogram", hist);
result.add("chunkHashes", chunkHashes);
File out = new File(Iris.instance.getDataFolder(), "genhash.json");
try {
Files.writeString(out.toPath(), result.toString());
} catch (IOException e) {
Iris.reportError(e);
}
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_DEVELOPER_GENHASH_GLOBAL_CHUNKS_SOLID, MessageArgument.untrusted("value", Long.toHexString(globalHash)), MessageArgument.untrusted("value2", (side * side)), MessageArgument.untrusted("solidBlocks", solidBlocks), MessageArgument.untrusted("value3", Form.duration((long) (M.ms() - startMs), 1))));
Iris.info("genhash world=" + world.getName() + " global=" + Long.toHexString(globalHash)
+ " chunks=" + (side * side) + " solidBlocks=" + solidBlocks + " -> " + out.getAbsolutePath());
}
private static long genHashMix(long z) {
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
return z ^ (z >>> 31);
}
@Director(description = "Stage a world generation update", descriptionKey = "iris.director.commanddeveloper.director.stage_world_generation_update", name = "update-world", aliases = "^world")
public void updateWorld(
@Param(description = "The world to update", descriptionKey = "iris.director.commanddeveloper.param.world_update", contextual = true, contextualOverride = true)
World world,
@Param(description = "The pack to install into the world", descriptionKey = "iris.director.commanddeveloper.param.pack_install_into_world", contextual = true, contextualOverride = true, aliases = "dimension")
IrisDimension pack,
@Param(description = "Make sure to make a backup & read the warnings first!", descriptionKey = "iris.director.commanddeveloper.param.make_sure_make_backup_read_warnings_first", defaultValue = "false", aliases = "c")
boolean confirm
) {
if (!confirm) {
sender().sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
MessageArgument.untrusted("world", world.getName()),
MessageArgument.untrusted("pack", pack.getLoadKey())
));
return;
}
File folder = world.getWorldFolder();
folder.mkdirs();
try (LifecycleOperationCoordinator.Lease lease = LifecycleOperationCoordinator.get().acquire(
LifecycleOperationCoordinator.Domain.PACK_MUTATION,
LifecycleOperationCoordinator.OperationKind.PACK_PUBLISH,
pack.getLoadKey()
)) {
Iris.service(StudioSVC.class).replaceIntoWorld(sender(), pack, folder, world.getSeed());
} catch (LifecycleOperationCoordinator.BusyException e) {
sender().sendMessage(C.YELLOW + e.getMessage());
}
}
@Director(description = "Test", descriptionKey = "iris.director.commanddeveloper.director.test")
public void mantle(
@Param(name = "plate", description = "Dump the whole tectonic plate instead of a single section", descriptionKey = "iris.director.commanddeveloper.param.dump_whole_tectonic_plate_instead_single_section", defaultValue = "false")
boolean plate,
@Param(name = "name", description = "The dump file id under plugins/Iris/dump (pv.<id>.*)", descriptionKey = "iris.director.commanddeveloper.param.dump_file_id_under_plugins_iris_dump_pv_id", defaultValue = "21474836474")
String name
) throws Throwable {
Engine activeEngine = engine();
if (activeEngine == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_TARGET_IRIS_WORLD_BEFORE_READING_MANTLE_DUMP));
return;
}
File base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin");
File section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin");
try (IrisMatterContext.Scope scope = IrisMatterContext.open(activeEngine.getData())) {
if (plate) {
try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) {
TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks());
} catch (Throwable e) {
Iris.reportError("Failed to inspect the Iris tectonic plate.", e);
}
} else {
Matter.read(section);
}
}
if (!TectonicPlate.hasError()) {
Iris.info("Read " + (plate ? base : section).length() + " bytes from " + (plate ? base : section).getAbsolutePath());
}
}
@Director(description = "Test", descriptionKey = "iris.director.commanddeveloper.director.test_2")
public void packBenchmark(
@Param(description = "The pack to bench", descriptionKey = "iris.director.commanddeveloper.param.pack_bench", aliases = {"pack"}, defaultValue = "overworld")
IrisDimension dimension,
@Param(description = "Radius in regions", descriptionKey = "iris.director.commanddeveloper.param.radius_regions", defaultValue = "2048")
int radius,
@Param(description = "Open GUI while benchmarking", descriptionKey = "iris.director.commanddeveloper.param.open_gui_while_benchmarking", defaultValue = "false")
boolean gui
) {
new IrisPackBenchmarking(dimension, radius, gui);
}
@Director(description = "Upgrade to another Minecraft version", descriptionKey = "iris.director.commanddeveloper.director.upgrade_another_minecraft_version")
public void upgrade(
@Param(description = "The version to upgrade to", descriptionKey = "iris.director.commanddeveloper.param.version_upgrade", defaultValue = "latest") DataVersion version) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_UPGRADING, MessageArgument.untrusted("value", version.getVersion())));
ServerConfigurator.installDataPacks(version.get(), false);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_DONE_UPGRADING_YOU_CAN_NOW_UPDATE_YOUR_SERVER_VERSION, MessageArgument.untrusted("value", version.getVersion())));
}
@Director(description = "test", descriptionKey = "iris.director.commanddeveloper.director.test_3")
public void mca (
@Param(description = "The world folder to scan for .mca region files", descriptionKey = "iris.director.commanddeveloper.param.world_folder_scan_mca_region_files") String world) {
try {
File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca"));
for (File mca : McaFiles) {
MCAFile MCARegion = MCAUtil.read(mca);
}
} catch (Exception e) {
Iris.reportError("Failed to inspect Minecraft region files.", e);
}
}
@Director(description = "Delete nearby chunk blocks for regen testing", descriptionKey = "iris.director.commanddeveloper.director.delete_nearby_chunk_blocks_regen_testing", name = "delete-chunk", aliases = {"dc"}, origin = DirectorOrigin.PLAYER)
public void deleteChunk(
@Param(description = "Radius in chunks around your current chunk", descriptionKey = "iris.director.commanddeveloper.param.radius_chunks_around_your_current_chunk", defaultValue = "0")
int radius
) {
if (radius < 0) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_RADIUS_MUST_BE_0_GREATER));
return;
}
Player player = player();
VolmitSender commandSender = sender();
World world = player.getWorld();
if (!IrisToolbelt.isIrisWorld(world)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_THIS_IS_NOT_IRIS_WORLD));
return;
}
PlatformChunkGenerator access = IrisToolbelt.access(world);
if (access == null || access.getEngine() == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_ENGINE_ACCESS_THIS_WORLD_IS_NULL));
return;
}
Engine engine = access.getEngine();
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
// The player position must be read on the thread owning the player; ChunkClearer hops per chunk itself.
if (!J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4;
int centerZ = player.getLocation().getBlockZ() >> 4;
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_DELETE_STARTED_CHUNK_S_AROUND_CLEARING_BLOCKS_AIR, MessageArgument.untrusted("chunks", chunks), MessageArgument.untrusted("centerX", centerX), MessageArgument.untrusted("centerZ", centerZ)));
new ChunkClearer(world, engine, commandSender, centerX, centerZ, radius).start();
})) {
Iris.warn("Could not schedule delete-chunk on the thread owning " + player.getName() + ".");
}
}
@Director(description = "Test", descriptionKey = "iris.director.commanddeveloper.director.test_4", aliases = {"ip"})
public void network() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface ni : Collections.list(networkInterfaces)) {
Iris.info("Display Name: %s", ni.getDisplayName());
Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
for (InetAddress ia : Collections.list(inetAddresses)) {
Iris.info("IP: %s", ia.getHostAddress());
}
}
} catch (Exception e) {
Iris.reportError("Failed to inspect server network interfaces.", e);
}
}
// --- Regen ---
@Director(name = "regen", aliases = {"rg"}, description = "Delete and regenerate nearby chunks in place using Iris generation", descriptionKey = "iris.director.commanddeveloper.director.delete_regenerate_nearby_chunks_place_using_iris_generation", origin = DirectorOrigin.PLAYER)
public void regen(
@Param(name = "radius", description = "The radius of nearby chunks", descriptionKey = "iris.director.commanddeveloper.param.radius_nearby_chunks", defaultValue = "5")
int radius
) {
if (radius < 0) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_RADIUS_MUST_BE_0_GREATER_2));
return;
}
Player player = player();
VolmitSender commandSender = sender();
World world = player.getWorld();
if (!IrisToolbelt.isIrisWorld(world)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_YOU_MUST_BE_IRIS_WORLD_USE_REGEN));
return;
}
Engine engine = IrisToolbelt.access(world).getEngine();
if (engine == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_ENGINE_ACCESS_THIS_WORLD_IS_NULL_GENERATE_NEARBY_CHUNKS_FIRST));
return;
}
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
// The player position must be read on the thread owning the player; the regenerator hops per chunk itself.
if (!J.runEntity(player, () -> {
int centerX = player.getLocation().getBlockX() >> 4;
int centerZ = player.getLocation().getBlockZ() >> 4;
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_REGEN_STARTED_CHUNK_S_AROUND_DELETING_REGENERATING_PLACE, MessageArgument.untrusted("chunks", chunks), MessageArgument.untrusted("centerX", centerX), MessageArgument.untrusted("centerZ", centerZ)));
Iris.info("Regen run start: world=" + world.getName()
+ " center=" + centerX + "," + centerZ
+ " radius=" + radius
+ " chunks=" + chunks);
new InPlaceChunkRegenerator(world, engine, commandSender, centerX, centerZ, radius).start();
})) {
Iris.warn("Could not schedule regen on the thread owning " + player.getName() + ".");
}
}
@Director(name = "goldenhash", aliases = {"gold"}, description = "Generate chunks into buffers (no world writes) and hash blocks+biomes; captures a golden file or verifies against an existing one. Deletes the world's entire mantle - use on disposable test worlds.", descriptionKey = "iris.director.commanddeveloper.director.generate_chunks_into_buffers_no_world_writes_hash_blocks_biomes_captures_golden", origin = DirectorOrigin.BOTH)
public void goldenhash(
@Param(description = "The world to scan", descriptionKey = "iris.director.commanddeveloper.param.world_scan", contextual = true, contextualOverride = true)
World world,
@Param(name = "radius", description = "Radius in chunks around the center", descriptionKey = "iris.director.commanddeveloper.param.radius_chunks_around_center_2", defaultValue = "8")
int radius,
@Param(name = "center-x", description = "Center chunk X", descriptionKey = "iris.director.commanddeveloper.param.center_chunk_x_2", defaultValue = "0")
int centerX,
@Param(name = "center-z", description = "Center chunk Z", descriptionKey = "iris.director.commanddeveloper.param.center_chunk_z_2", defaultValue = "0")
int centerZ,
@Param(name = "reset-mantle", description = "Delete the world's entire mantle folder first for full regeneration from scratch", descriptionKey = "iris.director.commanddeveloper.param.delete_mantle_data_scan_area_first_full_regeneration_from_scratch", defaultValue = "true")
boolean resetMantle,
@Param(name = "threads", description = "Concurrent chunk generations; 1 = strictly serial for order-dependence testing", descriptionKey = "iris.director.commanddeveloper.param.concurrent_chunk_generations_1_strictly_serial_order_dependence_testing", defaultValue = "8")
int threads,
@Param(name = "deep", description = "Also dump full per-chunk non-air blockstates for offline diffing", descriptionKey = "iris.director.commanddeveloper.param.also_dump_full_per_chunk_non_air_blockstates_offline_diffing", defaultValue = "false")
boolean deep
) {
if (radius < 0) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_RADIUS_MUST_BE_0_GREATER_3));
return;
}
if (world == null || !IrisToolbelt.isIrisWorld(world)) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_TARGET_MUST_BE_IRIS_WORLD));
return;
}
PlatformChunkGenerator access = IrisToolbelt.access(world);
if (access == null || access.getEngine() == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_ENGINE_ACCESS_THIS_WORLD_IS_NULL_2));
return;
}
int chunks = (radius * 2 + 1) * (radius * 2 + 1);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_DEVELOPER_GOLDENHASH_STARTED_CHUNK_S_AROUND_BUFFERS_WORLD_UNTOUCHED, MessageArgument.untrusted("chunks", chunks), MessageArgument.untrusted("centerX", centerX), MessageArgument.untrusted("centerZ", centerZ)));
Iris.info("goldenhash start: world=" + world.getName()
+ " center=" + centerX + "," + centerZ
+ " radius=" + radius
+ " chunks=" + chunks);
new GoldenHashScanner(world, access.getEngine(), sender(), centerX, centerZ, radius, resetMantle, threads, deep).start();
}
}
@@ -0,0 +1,121 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.localization.C;
import java.awt.Desktop;
import java.awt.GraphicsEnvironment;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.volmlib.util.localization.MessageArgument;
@Director(name = "edit", origin = DirectorOrigin.PLAYER, description = "Edit something", descriptionKey = "iris.director.commandedit.director.edit_something")
public class CommandEdit implements DirectorExecutor {
private boolean noStudio() {
if (!sender().isPlayer()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_PLAYERS_ONLY));
return true;
}
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_NO_STUDIO_WORLD_IS_OPEN));
return true;
}
if (!engine().isStudio()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_YOU_MUST_BE_STUDIO_WORLD));
return true;
}
if (GraphicsEnvironment.isHeadless()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANNOT_OPEN_FILES_HEADLESS_ENVIRONMENTS));
return true;
}
if (!Desktop.isDesktopSupported()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_DESKTOP_IS_NOT_SUPPORTED_BY_THIS_ENVIRONMENT));
return true;
}
return false;
}
@Director(description = "Edit the biome you specified", descriptionKey = "iris.director.commandedit.director.edit_biome_you_specified", aliases = {"b"}, origin = DirectorOrigin.PLAYER)
public void biome(@Param(contextual = false, description = "The biome to edit", descriptionKey = "iris.director.commandedit.param.biome_edit") IrisBiome biome) {
if (noStudio()) {
return;
}
try {
if (biome == null || biome.getLoadFile() == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANNOT_FIND_FILE_PERHAPS_IT_WAS_NOT_LOADED_DIRECTLY_FROM));
return;
}
Desktop.getDesktop().open(biome.getLoadFile());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_OPENING_VSCODE, MessageArgument.untrusted("value", biome.getTypeName()), MessageArgument.untrusted("value2", biome.getLoadFile().getName().split("\\Q.\\E")[0])));
} catch (Throwable e) {
Iris.reportError(e);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANT_FIND_FILE_REGISTRANT_DOES_NOT_EXIST));
}
}
@Director(description = "Edit the region you specified", descriptionKey = "iris.director.commandedit.director.edit_region_you_specified", aliases = {"r"}, origin = DirectorOrigin.PLAYER)
public void region(@Param(contextual = false, description = "The region to edit", descriptionKey = "iris.director.commandedit.param.region_edit") IrisRegion region) {
if (noStudio()) {
return;
}
try {
if (region == null || region.getLoadFile() == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANNOT_FIND_FILE_PERHAPS_IT_WAS_NOT_LOADED_DIRECTLY_FROM_2));
return;
}
Desktop.getDesktop().open(region.getLoadFile());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_OPENING_VSCODE_2, MessageArgument.untrusted("value", region.getTypeName()), MessageArgument.untrusted("value2", region.getLoadFile().getName().split("\\Q.\\E")[0])));
} catch (Throwable e) {
Iris.reportError(e);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANT_FIND_FILE_REGISTRANT_DOES_NOT_EXIST_2));
}
}
@Director(description = "Edit the dimension you specified", descriptionKey = "iris.director.commandedit.director.edit_dimension_you_specified", aliases = {"d"}, origin = DirectorOrigin.PLAYER)
public void dimension(@Param(contextual = false, description = "The dimension to edit", descriptionKey = "iris.director.commandedit.param.dimension_edit") IrisDimension dimension) {
if (noStudio()) {
return;
}
try {
if (dimension == null || dimension.getLoadFile() == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANNOT_FIND_FILE_PERHAPS_IT_WAS_NOT_LOADED_DIRECTLY_FROM_3));
return;
}
Desktop.getDesktop().open(dimension.getLoadFile());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_OPENING_VSCODE_3, MessageArgument.untrusted("value", dimension.getTypeName()), MessageArgument.untrusted("value2", dimension.getLoadFile().getName().split("\\Q.\\E")[0])));
} catch (Throwable e) {
Iris.reportError(e);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_EDIT_CANT_FIND_FILE_REGISTRANT_DOES_NOT_EXIST_3));
}
}
}
@@ -0,0 +1,863 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.pack.datapack.DatapackIngestService;
import art.arcane.iris.studio.object.ObjectStudioSaveService;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.placement.IrisStructureLocator;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
import art.arcane.iris.structure.placement.StructureReachability;
import art.arcane.iris.world.history.GenerationSemanticQueries;
import art.arcane.iris.generation.hydrology.HydrologyFeatureQuery;
import art.arcane.iris.generation.hydrology.runtime.IrisHydrologyRuntime;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationStatus;
import art.arcane.iris.platform.generation.EngineBukkitOps;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.command.specialhandlers.HydrologyTypeHandler;
import art.arcane.iris.world.history.GenerationFindCatalog;
import art.arcane.iris.command.specialhandlers.LocatableObjectHandler;
import art.arcane.iris.command.specialhandlers.ReachableBiomeHandler;
import art.arcane.iris.command.specialhandlers.ReachableRegionHandler;
import art.arcane.iris.command.specialhandlers.StructureHandler;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.localization.MessageArgument;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.generator.structure.Structure;
import org.bukkit.util.StructureSearchResult;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@Director(name = "find", origin = DirectorOrigin.PLAYER, description = "Iris Find commands", descriptionKey = "iris.director.commandfind.director.iris_find_commands", aliases = "goto")
public class CommandFind implements DirectorExecutor {
@Director(description = "Find a biome", descriptionKey = "iris.director.commandfind.director.find_biome")
public void biome(
@Param(description = "The biome to look for", descriptionKey = "iris.director.commandfind.param.biome_look", customHandler = ReachableBiomeHandler.class)
IrisBiome biome,
@Param(description = "Should you be teleported", descriptionKey = "iris.director.commandfind.param.should_you_be_teleported", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_NOT_IRIS_WORLD));
return;
}
EngineBukkitOps.gotoBiome(e, biome, player(), teleport);
}
@Director(description = "Find a region", descriptionKey = "iris.director.commandfind.director.find_region")
public void region(
@Param(description = "The region to look for", descriptionKey = "iris.director.commandfind.param.region_look", customHandler = ReachableRegionHandler.class)
IrisRegion region,
@Param(description = "Should you be teleported", descriptionKey = "iris.director.commandfind.param.should_you_be_teleported_2", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_NOT_IRIS_WORLD_2));
return;
}
EngineBukkitOps.gotoRegion(e, region, player(), teleport);
}
@Director(description = "Find a point of interest.", descriptionKey = "iris.director.commandfind.director.find_point_interest")
public void poi(
@Param(description = "The type of PoI to look for.", descriptionKey = "iris.director.commandfind.param.type_poi_look")
String type,
@Param(description = "Should you be teleported", descriptionKey = "iris.director.commandfind.param.should_you_be_teleported_3", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_NOT_IRIS_WORLD_3));
return;
}
EngineBukkitOps.gotoPOI(e, type, player(), teleport);
}
@Director(description = "Find an accepted hydrology feature", sync = true)
public void river(
@Param(description = "Feature type: surface, waterfall, underground, grotto, mouth, or a deep-fluid id", customHandler = HydrologyTypeHandler.class)
String type,
@Param(description = "Should you be teleported", defaultValue = "true")
boolean teleport
) {
Engine activeEngine = engine();
VolmitSender commandSender = sender();
Player target = player();
if (activeEngine == null || commandSender == null || target == null) {
if (commandSender != null) {
commandSender.sendMessage(C.RED + "Run this command from an Iris world.");
}
return;
}
IrisHydrologyRuntime runtime = activeEngine.getComplex().getHydrologyRuntime();
HydrologyFeatureQuery query;
try {
query = HydrologyFeatureQuery.parse(type);
} catch (IllegalArgumentException error) {
commandSender.sendMessage(C.RED + error.getMessage());
return;
}
Location origin = target.getLocation();
World originWorld = origin.getWorld();
int requestedDistance = runtime == null
? 8192
: Math.min(8192, runtime.settings().routing().tileSize() * 15);
int maximumDistance = runtime == null
? requestedDistance
: runtime.maximumFeatureSearchDistance(
origin.getBlockX(), origin.getBlockZ(), requestedDistance);
commandSender.sendMessage(C.GRAY + "Searching accepted hydrology plans for " + type + "...");
J.a(() -> {
try {
GenerationSemanticQueries.RiverResult feature = GenerationSemanticQueries.nearestRiver(
activeEngine,
query,
origin.getBlockX(),
origin.getBlockZ(),
maximumDistance,
(int visited) -> sendStructureMessage(target, commandSender,
C.GRAY + "Searched " + visited + " hydrology tiles for " + type + "...")
).orElse(null);
if (feature == null) {
sendStructureMessage(target, commandSender,
C.YELLOW + "No accepted " + type + " hydrology feature found within "
+ maximumDistance + " blocks.");
return;
}
String label = type + " hydrology feature";
if (!teleport) {
sendStructureMessage(target, commandSender,
C.GREEN + "Found " + label + " at " + feature.x() + ", " + feature.y() + ", " + feature.z() + ".");
return;
}
Location destination = new Location(
originWorld, feature.x(), feature.y(), feature.z());
prepareStructureTeleport(
target, originWorld, commandSender, label, destination, true);
} catch (Throwable error) {
sendStructureMessage(target, commandSender,
C.RED + "Could not locate " + type + " hydrology: " + error.getClass().getSimpleName());
Iris.reportError("Could not locate accepted hydrology feature '" + type + "'.", error);
}
});
}
@Director(description = "Find a structure (a vanilla key like minecraft:village_plains or minecraft:stronghold, or an imported iris structure key)", descriptionKey = "iris.director.commandfind.director.find_structure_vanilla_key_like_minecraft_village_plains_minecraft_stronghold_imported_iris", sync = true)
public void structure(
@Param(description = "The structure to look for (e.g. minecraft:village_plains, minecraft:stronghold, minecraft_ancient_city)", descriptionKey = "iris.director.commandfind.param.structure_look_e_g_minecraft_village_plains_minecraft_stronghold_minecraft_ancient_city", customHandler = StructureHandler.class)
String structure
) {
VolmitSender commandSender = sender();
if (commandSender == null) {
Iris.reportError("Structure lookup started without a command sender context.", new IllegalStateException("Missing command sender context"));
return;
}
Engine e = engine();
if (e == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_NOT_IRIS_WORLD));
return;
}
Player target = player();
if (target == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_RUN_THIS_GAME_TELEPORT_STRUCTURE));
return;
}
String structureKey = structure == null ? "" : structure.trim();
Structure nativeStructure = resolveNativeStructure(structureKey);
boolean registered = nativeStructure != null;
IrisNativeStructureDecision decision = registered
? NativeStructureGenerationPolicy.resolve(e, structureKey, false)
: null;
boolean nativePlacement = IrisStructureLocator.hasNativePlacement(e, structureKey);
boolean locatableNativePlacement = IrisStructureLocator.hasLocatableNativePlacement(e, structureKey);
boolean locatableEditablePlacement = IrisStructureLocator.hasLocatableEditablePlacement(e, structureKey);
World targetWorld = target.getWorld();
boolean nativeGenerationEnabled = targetWorld.canGenerateStructures();
boolean requiresReachability = registered && decision.generate()
&& decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS
&& nativeGenerationEnabled;
boolean reachable = !requiresReachability || StructureReachability.isReachable(e, structureKey);
StructureLookupRoute route = selectStructureLookupRoute(
registered, decision, nativePlacement, locatableNativePlacement,
locatableEditablePlacement, nativeGenerationEnabled, reachable);
if (route != StructureLookupRoute.NATIVE && route != StructureLookupRoute.IRIS
&& GenerationFindCatalog.hasRetainedStructurePlacement(e, structureKey)) {
locateIrisStructure(e, structureKey, commandSender);
return;
}
if (route == StructureLookupRoute.UNKNOWN) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_UNKNOWN_STRUCTURE, MessageArgument.untrusted("structureKey", structureKey)));
return;
}
if (route == StructureLookupRoute.POLICY_DISABLED) {
commandSender.sendMessage(C.RED + NativeStructureGenerationPolicy.generationStatusMessage(
structureKey, decision.status()));
return;
}
if (route == StructureLookupRoute.NO_ACTIVE_PLACEMENT) {
commandSender.sendMessage(C.YELLOW + structureKey
+ " has no active placement in this world.");
return;
}
if (route == StructureLookupRoute.WORLD_DISABLED) {
commandSender.sendMessage(C.YELLOW + structureKey
+ " cannot generate because native structure generation is disabled for this world.");
return;
}
if (route == StructureLookupRoute.UNREACHABLE) {
KList<String> miss = StructureReachability.missingBiomeKeys(e, structureKey);
commandSender.sendMessage(C.YELLOW + structureKey
+ " cannot generate in this world (its required biomes are not produced by this pack"
+ (miss.isEmpty() ? "" : ": needs " + String.join("/", miss)) + ").");
return;
}
if (route == StructureLookupRoute.IRIS) {
locateIrisStructure(e, structureKey, commandSender);
return;
}
Location origin = target.getLocation();
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_LOCATING, MessageArgument.untrusted("structureKey", structureKey)));
J.s(() -> {
try {
StructureSearchResult result = targetWorld.locateNearestStructure(
origin, nativeStructure, 100, false);
if (result == null || result.getLocation() == null) {
sendStructureMessage(target, commandSender,
C.YELLOW + "No " + structureKey + " found within range of you.");
return;
}
prepareStructureTeleport(
target, targetWorld, commandSender, structureKey, result.getLocation(), false);
} catch (Throwable t) {
sendStructureMessage(target, commandSender,
C.RED + "Could not locate " + structureKey + ": " + t.getClass().getSimpleName());
Iris.reportError("Could not locate structure '" + structureKey + "'.", t);
}
});
}
@Director(description = "Print every structure excluded from /iris goto and its rejection reason to the server console", sync = true)
public void unregistered() {
VolmitSender commandSender = sender();
if (commandSender == null) {
Iris.reportError("Structure exclusion report started without a command sender context.",
new IllegalStateException("Missing command sender context"));
return;
}
Engine activeEngine = engine();
if (activeEngine == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_NOT_IRIS_WORLD));
return;
}
Player target = player();
if (target == null) {
commandSender.sendMessage(C.RED + "Run this command from the Iris world to inspect its structures.");
return;
}
StructureExclusionReport snapshot;
try {
snapshot = collectStructureExclusions(
activeEngine, target.getWorld().canGenerateStructures());
} catch (Throwable error) {
commandSender.sendMessage(C.RED + "Could not build the structure exclusion report; see the server console.");
Iris.reportError("Could not snapshot /iris goto unregistered report for world '"
+ target.getWorld().getName() + "'.", error);
return;
}
String worldName = target.getWorld().getName();
J.a(() -> {
try {
StructureExclusionReport report = includeManagedDatapackStructures(
snapshot, DatapackIngestService.installed());
printStructureExclusionReport(worldName, report);
sendStructureMessage(target, commandSender, C.GREEN + "Printed " + report.entries().size()
+ " non-generating structure candidate(s) and their reasons to the server console. "
+ "This was an eligibility check; no chunks were searched.");
} catch (Throwable error) {
sendStructureMessage(target, commandSender,
C.RED + "Could not finish the structure exclusion report; see the server console.");
Iris.reportError("Could not finish /iris goto unregistered report for world '"
+ worldName + "'.", error);
}
});
}
private static StructureExclusionReport collectStructureExclusions(
Engine engine, boolean nativeGenerationEnabled) {
PlatformStructureHooks structureHooks = IrisPlatforms.get().structureHooks();
Map<String, String> registeredKeys = distinctStructureKeys(structureHooks.structureKeys());
Set<String> reachableKeys = StructureReachability.reachableKeys(engine);
Set<String> possibleBiomeKeys = normalizedStructureKeys(
structureHooks.possibleBiomeKeys(engine.getWorld().platformWorld()));
List<StructureExclusion> exclusions = new ArrayList<>();
int registeredExcluded = 0;
for (Map.Entry<String, String> entry : registeredKeys.entrySet()) {
String normalizedKey = entry.getKey();
String key = entry.getValue();
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine, key, false);
boolean nativePlacement = IrisStructureLocator.hasNativePlacement(engine, key);
boolean locatableNativePlacement = IrisStructureLocator.hasLocatableNativePlacement(engine, key);
boolean locatableEditablePlacement = IrisStructureLocator.hasLocatableEditablePlacement(engine, key);
StructureLookupRoute route = selectStructureLookupRoute(
true, decision, nativePlacement, locatableNativePlacement,
locatableEditablePlacement, nativeGenerationEnabled,
reachableKeys.contains(normalizedKey));
if (route == StructureLookupRoute.IRIS || route == StructureLookupRoute.NATIVE) {
continue;
}
List<String> requiredBiomes = needsNativeReachabilityReason(route, decision)
? structureHooks.structureBiomeKeys(key)
: List.of();
exclusions.add(new StructureExclusion(
StructureExclusionKind.EXCLUDED, key, null,
describeStructureExclusion(
route, key, decision.status(), nativePlacement,
requiredBiomes, possibleBiomeKeys)));
registeredExcluded++;
}
int configuredUnregistered = 0;
Set<String> configuredUnregisteredKeys = new LinkedHashSet<>();
for (String configuredKey : IrisStructureLocator.placedKeys(engine)) {
String normalizedKey = normalizeStructureKey(configuredKey);
if (normalizedKey.isEmpty()
|| registeredKeys.containsKey(normalizedKey)
|| !IrisStructureLocator.hasNativePlacement(engine, configuredKey)
|| !configuredUnregisteredKeys.add(normalizedKey)) {
continue;
}
exclusions.add(new StructureExclusion(
StructureExclusionKind.UNREGISTERED, configuredKey, null,
describeConfiguredUnregisteredNative(
IrisStructureLocator.hasLocatableNativePlacement(engine, configuredKey))));
configuredUnregistered++;
}
int editableUnplaced = 0;
Set<String> unplacedEditableKeys = new LinkedHashSet<>();
Set<String> locatableEditableKeys = normalizedStructureKeys(
IrisStructureLocator.locatableEditableKeys(engine));
for (String editableKey : engine.getData().getStructureLoader().getPossibleKeys()) {
String normalizedKey = normalizeStructureKey(editableKey);
if (!isUnplacedEditableCandidate(
normalizedKey, registeredKeys.keySet(), locatableEditableKeys,
IrisStructureLocator.hasNativePlacement(engine, editableKey))
|| !unplacedEditableKeys.add(normalizedKey)) {
continue;
}
boolean configuredPlacement = IrisStructureLocator.hasEditablePlacement(engine, editableKey);
String reason = configuredPlacement
? "editable Iris structure has a configured placement, but no matching placement is active "
+ "because its density is not positive or its Y band does not intersect this world"
: "editable Iris structure exists in this pack, but no structure placement references it";
exclusions.add(new StructureExclusion(
StructureExclusionKind.UNPLACED, editableKey, null, reason));
editableUnplaced++;
}
Set<String> configuredImportUrls = normalizedImportUrls(
DatapackIngestService.configuredImports(engine.getDimension()));
return new StructureExclusionReport(
List.copyOf(exclusions), Set.copyOf(registeredKeys.keySet()),
configuredImportUrls, registeredExcluded, configuredUnregistered,
editableUnplaced, 0);
}
static boolean isUnplacedEditableCandidate(
String normalizedKey, Set<String> registeredKeys,
Set<String> locatableEditableKeys, boolean nativePlacement) {
return normalizedKey != null
&& !normalizedKey.isEmpty()
&& !registeredKeys.contains(normalizedKey)
&& !locatableEditableKeys.contains(normalizedKey)
&& !nativePlacement;
}
private static StructureExclusionReport includeManagedDatapackStructures(
StructureExclusionReport snapshot, List<DatapackIngestService.Entry> installedEntries) {
Map<String, ManagedDatapackStructure> unregisteredStructures = new LinkedHashMap<>();
for (DatapackIngestService.Entry entry : installedEntries) {
String importUrl = normalizeImportUrl(entry.url);
if (importUrl.isEmpty() || !snapshot.configuredImportUrls().contains(importUrl)) {
continue;
}
String sourceId = entry.id == null || entry.id.isBlank() ? "unknown" : entry.id.trim();
List<String> structureKeys = entry.structureKeys == null ? List.of() : entry.structureKeys;
for (String key : structureKeys) {
String normalizedKey = normalizeStructureKey(key);
if (normalizedKey.isEmpty() || snapshot.registeredKeys().contains(normalizedKey)) {
continue;
}
ManagedDatapackStructure managed = unregisteredStructures.computeIfAbsent(
normalizedKey, ignored -> new ManagedDatapackStructure(key.trim()));
managed.sourceIds().add(sourceId);
}
}
List<StructureExclusion> entries = new ArrayList<>();
int configuredUnregistered = snapshot.configuredUnregistered();
for (StructureExclusion exclusion : snapshot.entries()) {
boolean replacedByManagedSource = exclusion.kind() == StructureExclusionKind.UNREGISTERED
&& unregisteredStructures.containsKey(normalizeStructureKey(exclusion.key()));
if (replacedByManagedSource) {
configuredUnregistered--;
continue;
}
entries.add(exclusion);
}
for (ManagedDatapackStructure managed : unregisteredStructures.values()) {
entries.add(new StructureExclusion(
StructureExclusionKind.UNREGISTERED, managed.key(),
String.join(",", managed.sourceIds()),
"declared by an Iris-managed datapack but absent from the live registry; "
+ "restart, enablement, or datapack validation may be required"));
}
sortStructureExclusions(entries);
return new StructureExclusionReport(
List.copyOf(entries), snapshot.registeredKeys(), snapshot.configuredImportUrls(),
snapshot.registeredExcluded(), Math.max(0, configuredUnregistered),
snapshot.editableUnplaced(), unregisteredStructures.size());
}
private static void printStructureExclusionReport(
String worldName, StructureExclusionReport report) {
Iris.info("Iris goto unregistered report for world '%s': %d non-generating structure candidate(s).",
worldName, report.entries().size());
for (StructureExclusion exclusion : report.entries()) {
String source = exclusion.source() == null
? ""
: " (source " + exclusion.source() + ")";
Iris.info("[%s] %s%s: %s",
exclusion.kind().label(), exclusion.key(), source, exclusion.reason());
}
Iris.info("Iris goto unregistered summary: %d registered key(s) excluded, "
+ "%d managed datapack key(s) unregistered, %d configured native key(s) unregistered, "
+ "%d editable Iris structure(s) unplaced.",
report.registeredExcluded(), report.managedUnregistered(),
report.configuredUnregistered(), report.editableUnplaced());
Iris.info("Eligibility report only; no chunks were searched and existing generated starts were not scanned.");
}
private static boolean needsNativeReachabilityReason(
StructureLookupRoute route, IrisNativeStructureDecision decision) {
return route == StructureLookupRoute.UNREACHABLE
|| route == StructureLookupRoute.NO_ACTIVE_PLACEMENT && decision.generate();
}
static String describeStructureExclusion(
StructureLookupRoute route, String key, NativeStructureGenerationStatus status,
boolean nativePlacement, List<String> requiredBiomes, Set<String> possibleBiomeKeys) {
return switch (route) {
case UNKNOWN -> "the key is not registered by the active server/datapack";
case POLICY_DISABLED -> NativeStructureGenerationPolicy.generationStatusMessage(key, status);
case WORLD_DISABLED -> "native structure generation is disabled for this world";
case UNREACHABLE -> nativeReachabilityReason(requiredBiomes, possibleBiomeKeys);
case NO_ACTIVE_PLACEMENT -> {
String placementType = nativePlacement
? "configured nativeStructures placement"
: "configured Iris replacement";
String reason = placementType + " is inactive: no matching placement has positive density "
+ "when density-based and a Y band intersecting this world";
if (status == NativeStructureGenerationStatus.GENERATE_NATIVE) {
reason += "; the native route is also inactive because "
+ nativeReachabilityReason(requiredBiomes, possibleBiomeKeys);
}
yield reason;
}
case IRIS, NATIVE -> throw new IllegalArgumentException(
"Active structure route cannot be described as excluded: " + route);
};
}
static String nativeReachabilityReason(
List<String> requiredBiomes, Set<String> possibleBiomeKeys) {
if (requiredBiomes == null || requiredBiomes.isEmpty()) {
return "its resolved biome filter is empty";
}
Set<String> possible = possibleBiomeKeys == null ? Set.of() : possibleBiomeKeys;
for (String requiredBiome : requiredBiomes) {
if (possible.contains(normalizeStructureKey(requiredBiome))) {
return "no active positive-weight, positive-frequency structure-set entry includes it in this world";
}
}
return "this pack does not produce any of its required biome(s): "
+ String.join("/", requiredBiomes);
}
static String describeConfiguredUnregisteredNative(boolean locatablePlacement) {
String reason = "configured in nativeStructures, but the key is not registered by the active "
+ "server/datapack, so Minecraft cannot create its native structure start";
return locatablePlacement
? reason
: reason + "; its Iris placement is also inactive because no matching placement has positive "
+ "density when density-based and a Y band intersecting this world";
}
private static Map<String, String> distinctStructureKeys(List<String> keys) {
Map<String, String> distinctKeys = new LinkedHashMap<>();
for (String key : keys) {
String normalizedKey = normalizeStructureKey(key);
if (!normalizedKey.isEmpty()) {
distinctKeys.putIfAbsent(normalizedKey, key.trim());
}
}
return distinctKeys;
}
private static Set<String> normalizedStructureKeys(Iterable<String> keys) {
Set<String> normalizedKeys = new LinkedHashSet<>();
if (keys == null) {
return normalizedKeys;
}
for (String key : keys) {
String normalizedKey = normalizeStructureKey(key);
if (!normalizedKey.isEmpty()) {
normalizedKeys.add(normalizedKey);
}
}
return normalizedKeys;
}
private static Set<String> normalizedImportUrls(Iterable<String> urls) {
Set<String> normalizedUrls = new LinkedHashSet<>();
if (urls == null) {
return normalizedUrls;
}
for (String url : urls) {
String normalizedUrl = normalizeImportUrl(url);
if (!normalizedUrl.isEmpty()) {
normalizedUrls.add(normalizedUrl);
}
}
return Set.copyOf(normalizedUrls);
}
private static String normalizeImportUrl(String url) {
return url == null ? "" : url.trim();
}
private static void sortStructureExclusions(List<StructureExclusion> exclusions) {
exclusions.sort((left, right) -> {
int kindComparison = left.kind().compareTo(right.kind());
return kindComparison == 0
? left.key().compareToIgnoreCase(right.key())
: kindComparison;
});
}
private static String normalizeStructureKey(String key) {
return key == null ? "" : key.trim().toLowerCase(Locale.ROOT);
}
static StructureLookupRoute selectStructureLookupRoute(
boolean registered, IrisNativeStructureDecision decision, boolean nativePlacement,
boolean locatableNativePlacement, boolean locatableEditablePlacement,
boolean nativeGenerationEnabled, boolean reachable) {
if (!registered) {
return !nativePlacement && locatableEditablePlacement
? StructureLookupRoute.IRIS
: StructureLookupRoute.UNKNOWN;
}
if (decision == null) {
throw new IllegalArgumentException("Registered structure lookup requires a generation decision");
}
if (!decision.generate()
&& decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
return StructureLookupRoute.POLICY_DISABLED;
}
if (decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
if (!nativePlacement) {
return locatableEditablePlacement
? StructureLookupRoute.IRIS
: StructureLookupRoute.NO_ACTIVE_PLACEMENT;
}
if (!nativeGenerationEnabled) {
return StructureLookupRoute.WORLD_DISABLED;
}
return locatableNativePlacement
? StructureLookupRoute.NATIVE
: StructureLookupRoute.NO_ACTIVE_PLACEMENT;
}
if (!nativeGenerationEnabled) {
return StructureLookupRoute.WORLD_DISABLED;
}
if (nativePlacement && locatableNativePlacement) {
return StructureLookupRoute.NATIVE;
}
if (reachable) {
return StructureLookupRoute.NATIVE;
}
return nativePlacement
? StructureLookupRoute.NO_ACTIVE_PLACEMENT
: StructureLookupRoute.UNREACHABLE;
}
private static Structure resolveNativeStructure(String structureKey) {
Registry<Structure> structureRegistry = Bukkit.getRegistry(Structure.class);
if (structureRegistry == null) {
return null;
}
for (Structure candidate : structureRegistry) {
NamespacedKey key = structureRegistry.getKey(candidate);
if (key != null && key.toString().equalsIgnoreCase(structureKey)) {
return candidate;
}
}
return null;
}
private void locateIrisStructure(Engine engine, String structure, VolmitSender commandSender) {
Player target = player();
if (target == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_RUN_THIS_GAME_TELEPORT_STRUCTURE_2));
return;
}
World targetWorld = target.getWorld();
Location origin = target.getLocation();
int blockX = origin.getBlockX();
int blockZ = origin.getBlockZ();
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_LOCATING_2, MessageArgument.untrusted("structure", structure)));
J.a(() -> {
try {
IrisStructureLocator.LocateResult result =
GenerationSemanticQueries.nearestStructure(engine, structure, blockX, blockZ, 1024);
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
sendStructureMessage(target, commandSender,
C.YELLOW + "Unable to locate " + structure
+ ": the density search safety limit was reached before the full 1024-chunk radius was searched.");
return;
}
if (!result.found()) {
sendStructureMessage(target, commandSender,
C.YELLOW + "No " + structure + " found within 1024 chunks of you.");
return;
}
Location destination = new Location(
targetWorld, result.originX(), result.baseY(), result.originZ());
prepareStructureTeleport(target, targetWorld, commandSender, structure, destination, true);
} catch (Throwable t) {
sendStructureMessage(target, commandSender,
C.RED + "Could not locate " + structure + ": " + t.getClass().getSimpleName());
Iris.reportError("Could not locate Iris-placed structure '" + structure + "'.", t);
}
});
}
@Director(description = "Find an object", descriptionKey = "iris.director.commandfind.director.find_object")
public void object(
@Param(description = "The object to look for", descriptionKey = "iris.director.commandfind.param.object_look", customHandler = LocatableObjectHandler.class)
String object,
@Param(description = "Should you be teleported", descriptionKey = "iris.director.commandfind.param.should_you_be_teleported_4", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_NOT_IRIS_WORLD_4));
return;
}
Player studioPlayer = player();
if (studioPlayer != null) {
try {
if (ObjectStudioSaveService.get().teleportTo(studioPlayer, object)) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_OBJECT_STUDIO_TELEPORTING, MessageArgument.untrusted("object", object)));
return;
}
} catch (Throwable t) {
Iris.reportError(t);
}
}
if (GenerationFindCatalog.hasObjectPlacement(e, object)) {
EngineBukkitOps.gotoObject(e, object, player(), teleport);
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_FIND_IS_NOT_CONFIGURED_ANY_REGION_BIOME_OBJECT_PLACEMENTS, MessageArgument.untrusted("object", object)));
}
private void prepareStructureTeleport(Player target, World world, VolmitSender commandSender, String structure,
Location at, boolean useLocatedY) {
J.runEntity(target, () -> {
if (!target.isOnline()) {
return;
}
if (!world.equals(target.getWorld())) {
commandSender.sendMessage(C.YELLOW + "Cancelled teleport to " + structure + " because your world changed.");
return;
}
loadStructureDestination(target, world, commandSender, structure, at, useLocatedY);
});
}
private void loadStructureDestination(Player target, World world, VolmitSender commandSender, String structure,
Location at, boolean useLocatedY) {
int chunkX = at.getBlockX() >> 4;
int chunkZ = at.getBlockZ() >> 4;
try {
BukkitPlatform.chunkAtAsync(world, chunkX, chunkZ, true).whenComplete((chunk, error) -> {
if (error != null) {
sendStructureMessage(target, commandSender, C.RED + "Could not load the destination for " + structure + ".");
Iris.reportError("Could not load structure destination '" + structure + "'.", error);
return;
}
boolean scheduled = J.runRegion(world, chunkX, chunkZ,
() -> teleportToStructure(target, world, commandSender, structure, at, useLocatedY));
if (!scheduled) {
sendStructureMessage(target, commandSender, C.RED + "Could not schedule the destination lookup for " + structure + ".");
}
});
} catch (Throwable error) {
sendStructureMessage(target, commandSender, C.RED + "Could not load the destination for " + structure + ".");
Iris.reportError("Could not load structure destination '" + structure + "'.", error);
}
}
private void teleportToStructure(Player target, World world, VolmitSender commandSender, String structure,
Location at, boolean useLocatedY) {
try {
int y = useLocatedY
? Math.max(world.getMinHeight() + 1, Math.min(world.getMaxHeight() - 1, at.getBlockY() + 2))
: world.getHighestBlockYAt(at.getBlockX(), at.getBlockZ()) + 2;
Location destination = new Location(world, at.getBlockX() + 0.5, y, at.getBlockZ() + 0.5);
J.runEntity(target, () -> completeStructureTeleport(target, world, commandSender, structure, destination));
} catch (Throwable t) {
sendStructureMessage(target, commandSender, C.RED + "Could not prepare the destination for " + structure + ".");
Iris.reportError("Could not prepare structure destination '" + structure + "'.", t);
}
}
private void completeStructureTeleport(Player target, World world, VolmitSender commandSender, String structure,
Location destination) {
if (!target.isOnline()) {
return;
}
if (!world.equals(target.getWorld())) {
commandSender.sendMessage(C.YELLOW + "Cancelled teleport to " + structure + " because your world changed.");
return;
}
try {
BukkitPlatform.teleportAsync(target, destination).whenComplete((teleported, error) -> {
if (error != null) {
Iris.reportError("Could not teleport to '" + structure + "'.", error);
}
sendStructureMessage(target, commandSender, error == null && Boolean.TRUE.equals(teleported)
? IrisLanguage.text(BukkitCommandMessages.COMMAND_FIND_TELEPORTED,
MessageArgument.untrusted("structure", structure),
MessageArgument.untrusted("value", destination.getBlockX()),
MessageArgument.untrusted("y", destination.getBlockY()),
MessageArgument.untrusted("value2", destination.getBlockZ()))
: C.RED + "Could not teleport to " + structure + ".");
});
} catch (Throwable error) {
commandSender.sendMessage(C.RED + "Could not teleport to " + structure + ".");
Iris.reportError("Could not teleport to '" + structure + "'.", error);
}
}
private void sendStructureMessage(Player target, VolmitSender commandSender, String message) {
J.runEntity(target, () -> commandSender.sendMessage(message));
}
enum StructureLookupRoute {
UNKNOWN,
POLICY_DISABLED,
NO_ACTIVE_PLACEMENT,
WORLD_DISABLED,
UNREACHABLE,
IRIS,
NATIVE
}
private enum StructureExclusionKind {
UNREGISTERED("unregistered"),
EXCLUDED("excluded"),
UNPLACED("unplaced");
private final String label;
StructureExclusionKind(String label) {
this.label = label;
}
private String label() {
return label;
}
}
private record StructureExclusion(
StructureExclusionKind kind, String key, String source, String reason) {
}
private record StructureExclusionReport(
List<StructureExclusion> entries, Set<String> registeredKeys,
Set<String> configuredImportUrls, int registeredExcluded,
int configuredUnregistered, int editableUnplaced, int managedUnregistered) {
}
private record ManagedDatapackStructure(String key, Set<String> sourceIds) {
private ManagedDatapackStructure(String key) {
this(key, new LinkedHashSet<>());
}
}
}
@@ -0,0 +1,890 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.world.BukkitWorldReconciler;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.world.IrisWorldStorage;
import art.arcane.iris.world.IrisWorlds;
import art.arcane.iris.world.PendingWorldReplacementManager;
import art.arcane.iris.pack.datapack.ServerConfigurator;
import art.arcane.iris.world.lifecycle.IrisWorldRemovalService;
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.world.lifecycle.WorldLifecycleService;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.PackDownloader;
import art.arcane.iris.pack.PackDirectoryResolver;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.iris.platform.generation.PlatformChunkGenerator;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.bukkit.WorldIdentity;
import art.arcane.volmlib.util.director.DirectorParameterHandler;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import art.arcane.iris.command.specialhandlers.NullablePlayerHandler;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.io.File;
import java.nio.file.Files;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import static art.arcane.iris.platform.bootstrap.ServerProperties.BUKKIT_YML;
import static org.bukkit.Bukkit.getServer;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.IrisMessages;
import art.arcane.iris.localization.PackDownloadMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.RuntimeUiMessages;
@Director(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command", descriptionKey = "iris.director.commandiris.director.basic_command")
public class CommandIris implements DirectorExecutor {
private static final String NO_DOWNLOAD_SOURCE = "__none__";
private static final String PRESERVE_REPLACEMENT_SEED = "preserve";
private static final long WORLD_UNLOAD_TIMEOUT_SECONDS = 150L;
private CommandStudio studio;
private CommandPregen pregen;
private CommandObject object;
private CommandStructure structure;
private CommandJigsaw jigsaw;
private CommandWhat what;
private CommandEdit edit;
private CommandDeveloper developer;
private CommandPack pack;
private CommandFind find;
private CommandDatapack datapack;
private CommandDebug debug;
VolmitSender sender = Iris.getSender();
@Director(description = "Choose your language or the server default")
public void language() {
Iris.instance.selectLanguage(sender().getS(), new String[0]);
}
@Director(description = "Create a new world", descriptionKey = "iris.director.commandiris.director.create_new_world", aliases = {"c"})
public void create(
@Param(aliases = "world-name", description = "The name of the world to create", descriptionKey = "iris.director.commandiris.param.name_world_create")
String name,
@Param(
aliases = {"dimension", "pack"},
description = "The dimension/pack to create the world with", descriptionKey = "iris.director.commandiris.param.dimension_pack_create_world_with",
defaultValue = "default",
customHandler = PackDimensionTypeHandler.class
)
String type,
@Param(description = "The seed to generate the world with", descriptionKey = "iris.director.commandiris.param.seed_generate_world_with", defaultValue = "1337")
long seed
) {
NamespacedKey worldKey;
try {
worldKey = IrisWorldStorage.managedKeyFromName(name);
IrisWorldStorage.requireSafeManagedDimensionRoot(worldKey);
} catch (IllegalArgumentException e) {
sender().sendMessage(C.RED + e.getMessage());
return;
}
String worldName = IrisWorldStorage.logicalName(worldKey);
if (worldName.equalsIgnoreCase("iris")) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_YOU_CANNOT_USE_WORLD_NAME_IRIS_CREATING_WORLDS_AS_IRIS));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_MAY_WE_SUGGEST_NAME_IRISWORLD_INSTEAD));
return;
}
if (worldName.equalsIgnoreCase("benchmark")) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_YOU_CANNOT_USE_WORLD_NAME_BENCHMARK_CREATING_WORLDS_AS_IRIS));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_MAY_WE_SUGGEST_NAME_IRISWORLD_INSTEAD_2));
return;
}
if (IrisWorldStorage.dimensionRoot(worldName).exists()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_THAT_FOLDER_ALREADY_EXISTS));
return;
}
Optional<String> startupDenial = ServerConfigurator.worldCreationDenialReason(false);
if (startupDenial.isPresent()) {
sender().sendMessage(C.YELLOW + startupDenial.get());
return;
}
String resolvedType = type.equalsIgnoreCase("default")
? IrisSettings.get().getGenerator().getDefaultWorldType()
: type;
IrisDimension dimension = IrisToolbelt.getDimension(resolvedType);
if (dimension == null) {
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_DIMENSION_NOT_FOUND,
MessageArgument.untrusted("dimension", resolvedType)
));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_TRY_ONE_OVERWORLD_VANILLA_FLAT_THEEND));
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_INSTALL_PACK_AND_RESTART,
MessageArgument.untrusted("command", PackDownloader.downloadCommandFor(resolvedType))
));
return;
}
try {
IrisToolbelt.createWorld()
.dimension(resolvedType)
.name(worldName)
.seed(seed)
.sender(sender())
.studio(false)
.create();
} catch (Throwable e) {
if (reportExpectedCreationInterruption(e)) {
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_EXCEPTION_RAISED_DURING_CREATION_SEE_CONSOLE_MORE_DETAILS));
Iris.reportError("Exception raised during world creation for \"" + worldName + "\".", e);
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_SUCCESSFULLY_CREATED_YOUR_WORLD));
}
@Director(
description = "Replace an existing world with Iris generation on the next restart",
descriptionKey = "iris.director.commandiris.param.replace_exact_existing_world_slot_next_restart",
aliases = {"override", "overwrite"}
)
public void replace(
@Param(
name = "target",
aliases = "world-name",
description = "The exact existing world slot to replace",
descriptionKey = "iris.director.commandiris.param.replace_exact_existing_world_slot_next_restart"
)
String target,
@Param(
aliases = {"dimension", "pack"},
description = "The dimension/pack to replace the world with",
defaultValue = "default",
customHandler = PackDimensionTypeHandler.class
)
String type,
@Param(
name = "seed",
aliases = "s",
description = "The replacement seed; omit to preserve the target world's seed",
defaultValue = PRESERVE_REPLACEMENT_SEED,
customHandler = ReplacementSeedHandler.class
)
Long seed
) {
NamespacedKey worldKey;
try {
worldKey = Iris.instance.pendingWorldReplacements().resolveRequestedWorldKey(target);
} catch (IllegalArgumentException e) {
sender().sendMessage(C.RED + e.getMessage());
return;
}
String resolvedType = type.equalsIgnoreCase("default")
? IrisSettings.get().getGenerator().getDefaultWorldType()
: type;
IrisDimension dimension = IrisToolbelt.getDimension(resolvedType);
if (dimension == null) {
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_DIMENSION_NOT_FOUND,
MessageArgument.untrusted("dimension", resolvedType)
));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_TRY_ONE_OVERWORLD_VANILLA_FLAT_THEEND));
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_INSTALL_PACK_AND_RESTART,
MessageArgument.untrusted("command", PackDownloader.downloadCommandFor(resolvedType))
));
return;
}
try {
PendingWorldReplacementManager.StagedReplacement staged = Iris.instance
.pendingWorldReplacements()
.stageReplacement(sender(), worldKey, dimension, seed);
String seedDetail = seed == null
? " preserving seed " + staged.seed()
: " using seed " + staged.seed();
sender().sendMessage(C.GREEN + "Staged Iris replacement for " + staged.worldKey()
+ seedDetail + ". Restart once to publish it. The current dimension is retained until Iris "
+ "verifies the replacement.");
} catch (LifecycleOperationCoordinator.BusyException busy) {
sender().sendMessage(C.YELLOW + busy.getMessage());
} catch (Throwable failure) {
Iris.reportError("Failed to stage Iris world replacement for " + worldKey + ".", failure);
String detail = failure.getMessage() == null || failure.getMessage().isBlank()
? failure.getClass().getSimpleName()
: failure.getMessage();
sender().sendMessage(C.RED + "Could not stage the world replacement: " + detail);
}
}
private boolean reportExpectedCreationInterruption(Throwable failure) {
Throwable current = failure;
while (current != null) {
if (current instanceof LifecycleOperationCoordinator.BusyException) {
sender().sendMessage(C.YELLOW + current.getMessage());
return true;
}
current = current.getCause();
}
String message = failure.getMessage();
if (message != null && message.contains("queued a restart")) {
sender().sendMessage(C.YELLOW + message);
return true;
}
return false;
}
@Director(description = "Teleport to another world", descriptionKey = "iris.director.commandiris.director.teleport_another_world", aliases = {"tp"}, sync = true)
public void teleport(
@Param(description = "World to teleport to", descriptionKey = "iris.director.commandiris.param.world_teleport")
World world,
@Param(description = "Player to teleport", descriptionKey = "iris.director.commandiris.param.player_teleport", defaultValue = "---", customHandler = NullablePlayerHandler.class)
Player player
) {
if (player == null && sender().isPlayer())
player = sender().player();
final Player target = player;
if (target == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_SPECIFIED_PLAYER_DOES_NOT_EXIST));
return;
}
final Location spawn = world.getSpawnLocation();
final Runnable teleportTask = () -> {
BukkitPlatform.teleportAsync(target, spawn);
new VolmitSender(target).sendMessage(C.GREEN + IrisLanguage.text(
RuntimeUiMessages.TELEPORTED_TO_WORLD,
MessageArgument.untrusted("world", world.getName())
));
};
if (!J.runEntity(target, teleportTask)) {
teleportTask.run();
}
}
@Director(hidden = true, sync = true, description = "Print version information", descriptionKey = "iris.director.commandiris.director.print_version_information")
public void version(@Param(name = "sender", contextual = true) CommandSender sender) {
debug.version(sender);
}
@Director(description = "Print world height information", descriptionKey = "iris.director.commandiris.director.print_world_height_information", origin = DirectorOrigin.PLAYER)
public void height() {
if (sender().isPlayer()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_TO, MessageArgument.untrusted("value", sender().player().getWorld().getMinHeight()), MessageArgument.untrusted("value2", sender().player().getWorld().getMaxHeight())));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_TOTAL_HEIGHT, MessageArgument.untrusted("value", (sender().player().getWorld().getMaxHeight() - sender().player().getWorld().getMinHeight()))));
} else {
World mainWorld = getServer().getWorlds().get(0);
Iris.info(C.GREEN + "" + mainWorld.getMinHeight() + " to " + mainWorld.getMaxHeight());
Iris.info(C.GREEN + "Total Height: " + (mainWorld.getMaxHeight() - mainWorld.getMinHeight()));
}
}
@Director(description = "Check access of all worlds.", descriptionKey = "iris.director.commandiris.director.check_access_all_worlds", aliases = {"accesslist"})
public void worlds() {
KList<World> IrisWorlds = new KList<>();
KList<World> BukkitWorlds = new KList<>();
for (World w : Bukkit.getServer().getWorlds()) {
try {
Engine engine = IrisToolbelt.access(w).getEngine();
if (engine != null) {
IrisWorlds.add(w);
}
} catch (Exception e) {
BukkitWorlds.add(w);
}
}
if (sender().isPlayer()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_IRIS_WORLDS));
for (World IrisWorld : IrisWorlds.copy()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_MESSAGE, MessageArgument.untrusted("value", IrisWorld.getName())));
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_BUKKIT_WORLDS));
for (World BukkitWorld : BukkitWorlds.copy()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_MESSAGE_2, MessageArgument.untrusted("value", BukkitWorld.getName())));
}
} else {
Iris.info(C.BLUE + "Iris Worlds: ");
for (World IrisWorld : IrisWorlds.copy()) {
Iris.info(C.IRIS + "- " +IrisWorld.getName());
}
Iris.info(C.GOLD + "Bukkit Worlds: ");
for (World BukkitWorld : BukkitWorlds.copy()) {
Iris.info(C.GRAY + "- " +BukkitWorld.getName());
}
}
}
@Director(description = "Remove an Iris world", descriptionKey = "iris.director.commandiris.director.remove_iris_world", aliases = {"rm"}, sync = true)
public void remove(
@Param(description = "The loaded or disk-only Iris world to remove", descriptionKey = "iris.director.commandiris.param.world_remove", customHandler = ManagedWorldNameHandler.class)
String world,
@Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", descriptionKey = "iris.director.commandiris.param.whether_also_remove_folder_if_set_false_just_does_not_load_world", defaultValue = "true")
boolean delete
) {
VolmitSender responseSender = sender();
responseSender.sendMessage(C.GRAY + "Removing Iris world '" + world + "'...");
IrisWorldRemovalService.get().remove(world, delete).whenComplete((result, throwable) -> {
Runnable response = () -> reportRemovalResult(responseSender, world, result, throwable);
if (responseSender.isPlayer() && J.runEntity(responseSender.player(), response)) {
return;
}
J.s(response);
});
}
private void reportRemovalResult(
VolmitSender responseSender,
String requestedWorld,
IrisWorldRemovalService.RemovalResult result,
Throwable throwable
) {
if (throwable != null || result == null) {
Throwable failure = throwable == null
? new IllegalStateException("World removal returned no result.")
: throwable;
responseSender.sendMessage(C.RED + "World removal failed unexpectedly; nothing further was deleted.");
Iris.reportError("Unexpected world removal failure for \"" + requestedWorld + "\".", failure);
return;
}
switch (result.status()) {
case UNREGISTERED -> responseSender.sendMessage(C.GREEN + "Unloaded and unregistered '"
+ result.target().logicalName() + "'; its files were preserved.");
case DELETED -> responseSender.sendMessage(C.GREEN + "Removed Iris world '"
+ result.target().logicalName() + "' and deleted its folder.");
case DELETE_QUEUED -> responseSender.sendMessage(C.YELLOW + "Removed Iris world '"
+ result.target().logicalName() + "'; its quarantined folder will be deleted at startup.");
case BUSY -> responseSender.sendMessage(C.YELLOW + "World changes are busy with "
+ result.blockingOperation().kind().name().toLowerCase(Locale.ROOT) + " for '"
+ result.blockingOperation().target() + "'. Try again when it completes.");
case INVALID_IDENTIFIER, PROTECTED_WORLD, NOT_IRIS_WORLD, UNSAFE_PATH, NOT_FOUND ->
responseSender.sendMessage(C.RED + removalFailureDetail(result));
default -> {
responseSender.sendMessage(C.RED + "World removal stopped at "
+ result.status().name().toLowerCase(Locale.ROOT) + ": " + removalFailureDetail(result));
if (result.quarantineDirectory() != null) {
responseSender.sendMessage(C.YELLOW + "The recoverable world folder is "
+ result.quarantineDirectory().toAbsolutePath() + ".");
} else if (result.configurationChanged() || result.registryChanged()) {
responseSender.sendMessage(C.YELLOW + "Removal changed registration state before stopping; "
+ "the original world folder was not deleted.");
}
if (result.failure() != null) {
Iris.reportError("World removal failed for \"" + requestedWorld + "\" at "
+ result.status().name() + ".", result.failure());
}
}
}
}
private String removalFailureDetail(IrisWorldRemovalService.RemovalResult result) {
Throwable failure = result.failure();
if (failure == null || failure.getMessage() == null || failure.getMessage().isBlank()) {
return result.status().name().toLowerCase(Locale.ROOT).replace('_', ' ');
}
return failure.getMessage();
}
@Director(description = "Download a project.", descriptionKey = "iris.director.commandiris.director.download_project", aliases = "dl")
public void download(
@Param(name = "pack", description = "The built-in pack to download", descriptionKey = "iris.director.commandiris.param.pack_download", defaultValue = NO_DOWNLOAD_SOURCE, customHandler = DownloadPackHandler.class)
String pack,
@Param(name = "link", description = "A direct HTTP or HTTPS zip link", defaultValue = NO_DOWNLOAD_SOURCE)
String link
) {
String builtInPack = NO_DOWNLOAD_SOURCE.equals(pack) ? null : pack;
String directLink = NO_DOWNLOAD_SOURCE.equals(link) ? null : link;
if ((builtInPack == null) == (directLink == null)) {
sender().sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_SOURCE));
return;
}
if (builtInPack != null) {
Iris.service(StudioSVC.class).downloadBuiltIn(sender(), builtInPack);
return;
}
if (!PackDownloader.isDirectZipUrl(directLink)) {
sender().sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_URL));
return;
}
Iris.service(StudioSVC.class).downloadUrl(sender(), directLink);
}
@Director(description = "Get metrics for your world", descriptionKey = "iris.director.commandiris.director.get_metrics_your_world", aliases = "measure", origin = DirectorOrigin.PLAYER)
public void metrics() {
if (!IrisToolbelt.isIrisWorld(world())) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_YOU_MUST_BE_IRIS_WORLD));
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_SENDING_METRICS));
engine().printMetrics(sender()::sendMessage);
}
@Director(description = "Reload configuration file (this is also done automatically)", descriptionKey = "iris.director.commandiris.director.reload_configuration_file_this_is_also_done_automatically")
public void reload() {
IrisSettings.invalidate();
IrisSettings.get();
boolean localeLoaded = IrisLanguage.reload();
if (localeLoaded) {
sender().sendMessage(C.GREEN + IrisLanguage.text(
IrisMessages.COMMAND_RELOAD_SUCCESS,
MessageArgument.trusted("locale", IrisLanguage.activeLocale())
));
return;
}
sender().sendMessage(C.YELLOW + IrisLanguage.text(
IrisMessages.COMMAND_RELOAD_FAILED,
MessageArgument.untrusted("locale", IrisSettings.get().getGeneral().getLanguage()),
MessageArgument.trusted("activeLocale", IrisLanguage.activeLocale())
));
}
@Director(description = "Unload an Iris World", descriptionKey = "iris.director.commandiris.director.unload_iris_world", origin = DirectorOrigin.PLAYER, sync = true)
public void unloadWorld(
@Param(description = "The world to unload", descriptionKey = "iris.director.commandiris.param.world_unload")
World world
) {
if (!IrisToolbelt.isIrisWorld(world)) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_THIS_IS_NOT_IRIS_WORLD_IRIS_WORLDS_2, MessageArgument.untrusted("value", String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()))));
return;
}
VolmitSender responseSender = sender();
responseSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_UNLOADING_WORLD, MessageArgument.untrusted("value", world.getName())));
LifecycleOperationCoordinator.Lease lease;
try {
lease = LifecycleOperationCoordinator.get().acquire(
LifecycleOperationCoordinator.Domain.WORLD_MUTATION,
LifecycleOperationCoordinator.OperationKind.WORLD_UNLOAD,
WorldIdentity.serialize(world)
);
} catch (LifecycleOperationCoordinator.BusyException e) {
responseSender.sendMessage(C.YELLOW + e.getMessage());
return;
}
PlatformChunkGenerator generator = IrisToolbelt.access(world);
IrisToolbelt.beginWorldMaintenance(world, "world-unload", true);
try {
AtomicBoolean terminalTimeout = new AtomicBoolean(false);
CompletableFuture<Boolean> sequence = IrisToolbelt.evacuateAsync(world)
.thenCompose(evacuated -> {
if (terminalTimeout.get()) {
return CompletableFuture.failedFuture(new TimeoutException(
"World unload stopped after its terminal timeout."));
}
if (!Boolean.TRUE.equals(evacuated)) {
return CompletableFuture.completedFuture(false);
}
return WorldLifecycleService.get().unloadAsync(world, true);
})
.thenCompose(unloaded -> {
if (terminalTimeout.get()) {
return CompletableFuture.failedFuture(new TimeoutException(
"World unload stopped after its terminal timeout."));
}
if (!Boolean.TRUE.equals(unloaded) || generator == null) {
return CompletableFuture.completedFuture(Boolean.TRUE.equals(unloaded));
}
return generator.closeAsync().thenApply(ignored -> true);
});
guardUnloadCompletion(sequence, terminalTimeout, world.getName())
.whenComplete((unloaded, throwable) -> {
IrisToolbelt.endWorldMaintenance(world, "world-unload", true);
lease.close();
Runnable response = () -> reportUnloadResult(responseSender, world, unloaded, throwable);
if (responseSender.isPlayer() && J.runEntity(responseSender.player(), response)) {
return;
}
J.s(response);
});
} catch (Exception e) {
IrisToolbelt.endWorldMaintenance(world, "world-unload", true);
lease.close();
responseSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_UNLOAD_WORLD_3, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
Iris.reportError("Failed to unload world \"" + world.getName() + "\".", e);
}
}
private CompletableFuture<Boolean> guardUnloadCompletion(
CompletableFuture<Boolean> source,
AtomicBoolean terminalTimeout,
String worldName
) {
CompletableFuture<Boolean> guarded = new CompletableFuture<>();
AtomicBoolean settled = new AtomicBoolean(false);
source.whenComplete((unloaded, throwable) -> {
if (!settled.compareAndSet(false, true)) {
return;
}
if (throwable == null) {
guarded.complete(Boolean.TRUE.equals(unloaded));
} else {
guarded.completeExceptionally(throwable);
}
});
CompletableFuture.delayedExecutor(WORLD_UNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS).execute(() -> {
if (!settled.compareAndSet(false, true)) {
return;
}
terminalTimeout.set(true);
TimeoutException timeout = new TimeoutException(
"World unload did not settle within " + WORLD_UNLOAD_TIMEOUT_SECONDS
+ " seconds for \"" + worldName + "\".");
ServerConfigurator.restart("World unload timed out for \"" + worldName + "\".");
guarded.completeExceptionally(timeout);
});
return guarded;
}
private void reportUnloadResult(VolmitSender responseSender, World world, Boolean unloaded, Throwable throwable) {
if (throwable != null) {
responseSender.sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_UNLOAD_WORLD_3,
MessageArgument.untrusted("value", String.valueOf(throwable.getMessage()))
));
Iris.reportError("Failed to unload world \"" + world.getName() + "\".", throwable);
return;
}
if (Boolean.TRUE.equals(unloaded)) {
responseSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_WORLD_UNLOADED_SUCCESSFULLY));
} else {
responseSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_UNLOAD_WORLD_2));
}
}
@Director(description = "Load an Iris World", descriptionKey = "iris.director.commandiris.director.load_iris_world", origin = DirectorOrigin.PLAYER, sync = true, aliases = {"import"})
public void loadWorld(
@Param(
description = "The name of the world to load",
descriptionKey = "iris.director.commandiris.param.name_world_load",
customHandler = ManagedWorldNameHandler.class)
String world
) {
NamespacedKey worldKey;
try {
worldKey = IrisWorldStorage.managedKeyFromName(world);
IrisWorldStorage.requireSafeManagedDimensionRoot(worldKey);
} catch (IllegalArgumentException failure) {
sender().sendMessage(C.RED + failure.getMessage());
return;
}
String logicalWorldName = IrisWorldStorage.logicalName(worldKey);
boolean worldExists = doesWorldExist(logicalWorldName);
if (!worldExists) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_DOESNT_EXIST_ON_SERVER, MessageArgument.untrusted("logicalWorldName", logicalWorldName)));
return;
}
VolmitSender responseSender = sender();
responseSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_LOADING_WORLD, MessageArgument.untrusted("logicalWorldName", logicalWorldName)));
Iris.instance.worldReconciler()
.loadWorld(BUKKIT_YML, worldKey.toString())
.whenComplete((result, failure) -> {
Runnable response = () -> reportLoadWorldResult(
responseSender,
logicalWorldName,
result,
failure);
if (responseSender.isPlayer() && J.runEntity(responseSender.player(), response)) {
return;
}
J.s(response);
});
}
private void reportLoadWorldResult(
VolmitSender responseSender,
String logicalWorldName,
BukkitWorldReconciler.LoadResult result,
Throwable failure
) {
if (failure != null) {
responseSender.sendMessage(C.RED + "Failed to load Iris world \"" + logicalWorldName + "\": " + failure.getMessage());
Iris.reportError("Failed to load Iris world \"" + logicalWorldName + "\".", failure);
return;
}
if (result == null) {
IllegalStateException missingResult = new IllegalStateException("World load completed without a result.");
responseSender.sendMessage(C.RED + missingResult.getMessage());
Iris.reportError("Failed to load Iris world \"" + logicalWorldName + "\".", missingResult);
return;
}
if (result.succeeded()) {
responseSender.sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_IRIS_LOADED_SUCCESSFULLY,
MessageArgument.untrusted("logicalWorldName", logicalWorldName)));
return;
}
C color = result.status() == BukkitWorldReconciler.ReconciliationStatus.BUSY
|| result.status() == BukkitWorldReconciler.ReconciliationStatus.RESTART_REQUIRED
? C.YELLOW
: C.RED;
responseSender.sendMessage(color + result.message());
Throwable resultFailure = result.failure();
if (resultFailure != null
&& result.status() != BukkitWorldReconciler.ReconciliationStatus.BUSY
&& result.status() != BukkitWorldReconciler.ReconciliationStatus.RESTART_REQUIRED) {
Iris.reportError("Failed to load Iris world \"" + logicalWorldName + "\".", resultFailure);
}
}
@Director(description = "Evacuate an iris world", descriptionKey = "iris.director.commandiris.director.evacuate_iris_world", origin = DirectorOrigin.PLAYER, sync = true)
public void evacuate(
@Param(description = "Evacuate the world", descriptionKey = "iris.director.commandiris.param.evacuate_world")
World world
) {
if (!IrisToolbelt.isIrisWorld(world)) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_THIS_IS_NOT_IRIS_WORLD_IRIS_WORLDS_3, MessageArgument.untrusted("value", String.join(", ", getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList()))));
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_EVACUATING_WORLD, MessageArgument.untrusted("value", world.getName())));
IrisToolbelt.evacuate(world);
}
boolean doesWorldExist(String worldName) {
NamespacedKey worldKey = IrisWorldStorage.managedKeyFromName(worldName);
File levelRoot = IrisWorldStorage.levelRoot();
return IrisWorldStorage.frozenDimensionRoot(
Bukkit.getWorldContainer(),
levelRoot,
IrisWorldStorage.configuredWorldName(worldKey, levelRoot.getName()),
worldKey
).isPresent();
}
public static class ManagedWorldNameHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
Set<String> options = new LinkedHashSet<>();
for (World world : Bukkit.getWorlds()) {
if (!IrisToolbelt.isIrisWorld(world)) {
continue;
}
options.add(IrisWorldStorage.logicalName(world));
String startupName = configuredStartupName(world);
if (startupName != null) {
options.add(startupName);
}
}
for (String identity : IrisWorlds.get().getWorlds().keySet()) {
try {
options.add(IrisWorldStorage.logicalName(WorldIdentity.parse(identity)));
} catch (IllegalArgumentException ignored) {
}
}
File namespace = new File(IrisWorldStorage.levelRoot(), "dimensions/iris");
File[] diskWorlds = namespace.listFiles(File::isDirectory);
if (diskWorlds != null) {
for (File diskWorld : diskWorlds) {
if (!Files.isSymbolicLink(diskWorld.toPath())
&& diskWorld.getName().matches("[a-z0-9_-]+")) {
options.add(diskWorld.getName());
}
}
}
return new KList<>(options);
}
/**
* "iris worlds" prints the Bukkit startup name, so it has to be offered and accepted here too.
*/
private static String configuredStartupName(World world) {
try {
String configured = IrisWorldStorage.configuredWorldName(
WorldIdentity.key(world),
IrisWorldStorage.levelRoot().getName()
);
return configured.equals(world.getName()) ? configured : null;
} catch (RuntimeException failure) {
return null;
}
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String in, boolean force) throws DirectorParsingException {
if (in == null || in.isBlank()) {
throw new DirectorParsingException("World identifier cannot be empty");
}
return in.trim();
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
public static class PackDimensionTypeHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return packDimensionOptions(Iris.instance.getDataFolder("packs"));
}
static KList<String> packDimensionOptions(File packsFolder) {
Set<String> options = new LinkedHashSet<>();
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsFolder)) {
options.add(pack.getName());
try {
IrisData data = IrisData.get(pack);
for (String key : data.getDimensionLoader().getPossibleKeys()) {
options.add(packDimensionOption(pack.getName(), key));
}
} catch (Throwable ex) {
Iris.warn("Failed to read dimension keys from pack %s: %s%s",
pack.getName(),
ex.getClass().getSimpleName(),
ex.getMessage() == null ? "" : " - " + ex.getMessage());
Iris.reportError(ex);
}
}
return new KList<>(options);
}
static String packDimensionOption(String packName, String dimensionKey) {
return packName.equalsIgnoreCase(dimensionKey)
? packName
: packName + ":" + dimensionKey;
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String in, boolean force) throws DirectorParsingException {
if (in == null || in.trim().isEmpty()) {
throw new DirectorParsingException("World type cannot be empty");
}
return in.trim();
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
public static class ReplacementSeedHandler implements DirectorParameterHandler<Long> {
@Override
public KList<Long> getPossibilities() {
return null;
}
@Override
public String toString(Long value) {
return value == null ? PRESERVE_REPLACEMENT_SEED : Long.toString(value);
}
@Override
public Long parse(String in, boolean force) throws DirectorParsingException {
String seed = in == null ? "" : in.trim();
if (PRESERVE_REPLACEMENT_SEED.equalsIgnoreCase(seed)) {
return null;
}
try {
return Long.parseLong(seed);
} catch (NumberFormatException failure) {
throw new DirectorParsingException("Seed must be a signed 64-bit integer");
}
}
@Override
public boolean supports(Class<?> type) {
return type == Long.class;
}
@Override
public String getRandomDefault() {
return "1337";
}
}
public static class DownloadPackHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return new KList<>(PackDownloader.builtInPacks());
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String in, boolean force) throws DirectorParsingException {
if (NO_DOWNLOAD_SOURCE.equals(in)) {
return null;
}
String pack = in == null ? "" : in.trim().toLowerCase(Locale.ROOT);
if (!PackDownloader.isBuiltInPack(pack)) {
throw new DirectorParsingException("Pack must be 'overworld' or 'underworld'");
}
return pack;
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
}
@@ -0,0 +1,815 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.studio.object.StudioOpenCoordinator;
import art.arcane.iris.studio.jigsaw.JigsawPlanarArchetype;
import art.arcane.iris.studio.jigsaw.JigsawStudioActivation;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.studio.jigsaw.JigsawStudioCompatibilityTarget;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphMapper;
import art.arcane.iris.studio.jigsaw.JigsawStudioLayout;
import art.arcane.iris.studio.jigsaw.JigsawStudioMode;
import art.arcane.iris.studio.jigsaw.JigsawStudioProjectCreator;
import art.arcane.iris.studio.jigsaw.JigsawStudioSession;
import art.arcane.iris.studio.jigsaw.JigsawStudioVariant;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphEvaluation;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.structure.StructureImporter;
import art.arcane.iris.structure.VillageImporter;
import art.arcane.iris.structure.authoring.StructureKey;
import art.arcane.iris.structure.authoring.StructureWriteResult;
import art.arcane.iris.structure.export.VanillaJigsawExportFormat;
import art.arcane.iris.structure.export.VanillaJigsawExportRequest;
import art.arcane.iris.structure.export.VanillaJigsawExportSource;
import art.arcane.iris.structure.graph.PlanarJigsawWorkcellResolver;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.jigsaw.IrisJigsawCompatibility;
import art.arcane.iris.structure.jigsaw.IrisJigsawPiece;
import art.arcane.iris.structure.jigsaw.IrisJigsawThemeSet;
import art.arcane.iris.structure.jigsaw.IrisJigsawWorkcellArchetype;
import art.arcane.iris.structure.object.IrisObject;
import art.arcane.iris.pack.value.IrisPosition;
import art.arcane.iris.structure.placement.IrisStructure;
import art.arcane.iris.command.specialhandlers.IrisStructureHandler;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.DirectorParameterHandler;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Director(name = "jigsaw", aliases = {"jig", "jgs"}, description = "Iris Jigsaw Studio tools",
origin = DirectorOrigin.PLAYER)
public class CommandJigsaw implements DirectorExecutor {
static final long DEFAULT_STUDIO_SEED = 1337L;
static final StudioOpenCoordinator.StudioOpenKind STUDIO_OPEN_KIND =
StudioOpenCoordinator.StudioOpenKind.JIGSAW;
static final Map<UUID, SessionBinding> PLAYER_PACKS = new ConcurrentHashMap<>();
private CommandJigsawPiece piece;
private CommandJigsawPool pool;
private CommandJigsawConnector connector;
private CommandJigsawVariant variant;
private CommandJigsawWorkcell workcell;
private CommandJigsawRules rules;
private CommandJigsawPreview preview;
private CommandJigsawAdopt adopt;
@Director(description = "Create and open a new atomic Jigsaw Studio project", sync = true,
origin = DirectorOrigin.PLAYER)
public void create(
@Param(description = "Pack dimension") IrisDimension dimension,
@Param(name = "key", aliases = {"structure", "name"},
description = "New key written as structures/<key>.json") String structure,
@Param(description = "planar or spatial", defaultValue = "planar",
customHandler = JigsawModeHandler.class) String mode,
@Param(description = "iris or vanilla", defaultValue = "iris",
customHandler = JigsawCompatibilityHandler.class) String compatibility,
@Param(description = "Cell width", defaultValue = "15") int width,
@Param(description = "Cell height", defaultValue = "15") int height,
@Param(description = "Cell depth", defaultValue = "15") int depth,
@Param(description = "Studio world seed", defaultValue = "1337") long seed
) {
IrisData data = requireData(dimension);
if (data == null) {
return;
}
JigsawStudioMode studioMode;
JigsawStudioCompatibilityTarget target;
JigsawStudioCellDimensions dimensions;
try {
studioMode = parseMode(mode);
target = parseCompatibility(compatibility);
dimensions = new JigsawStudioCellDimensions(width, height, depth);
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
StructureWriteResult result;
try {
result = JigsawStudioProjectCreator.create(
data.getDataFolder().toPath(),
new JigsawStudioProjectCreator.Options(structure, studioMode, target, dimensions));
} catch (IOException | RuntimeException exception) {
Iris.reportError("Failed to create Jigsaw Studio project '" + structure + "'.", exception);
sendError("Jigsaw project creation failed: " + exception.getMessage());
return;
}
if (!result.successful()) {
sendError("Jigsaw project was not created: " + result.status());
if (!result.conflicts().isEmpty()) {
sendError(result.conflicts().getFirst().relativePath() + ": "
+ result.conflicts().getFirst().reason());
}
return;
}
try {
JigsawStudioService.clearAutosaveHistory(
data.getDataFolder().toPath(),
structure);
} catch (IOException exception) {
Iris.reportError("Failed to clear stale Jigsaw Studio history for '" + structure + "'.", exception);
sendError("Jigsaw project was created, but stale autosave history could not be cleared: "
+ exception.getMessage());
return;
}
data.invalidateStructureResources();
sender().sendMessage(C.GREEN + "Created Jigsaw project '" + structure + "' atomically.");
open(dimension, structure, seed);
}
@Director(description = "Import a registered vanilla or datapack jigsaw graph into Iris Studio",
aliases = {"import", "import-vanilla"}, sync = true, origin = DirectorOrigin.PLAYER)
public void convert(
@Param(description = "Pack dimension") IrisDimension dimension,
@Param(description = "Registered structure key such as minecraft:village_plains") String source,
@Param(description = "New Iris structure key, or auto", defaultValue = "auto") String target,
@Param(description = "Studio world seed", defaultValue = "1337") long seed
) {
if (studioIsActiveOrOpening()) {
sendError("Close the active or opening Jigsaw Studio before importing another graph.");
return;
}
IrisData data = requireData(dimension);
if (data == null) {
return;
}
NamespacedKey sourceKey;
String targetKey;
try {
sourceKey = parseRegisteredStructureKey(source);
targetKey = resolveConversionTarget(sourceKey, target);
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
VillageImporter.Result result = VillageImporter.importVillage(
data,
sourceKey,
targetKey,
StructureImporter.Mode.ADD_ONLY);
if (!result.success()) {
sendError(conversionFailureMessage(result));
return;
}
data.invalidateStructureResources();
VolmitSender commandSender = sender();
commandSender.sendMessage(C.GREEN + result.message());
CommandJigsawOpen.openProject(player(), commandSender, dimension, targetKey, seed);
}
@Director(description = "Open an existing Iris jigsaw graph in transient Studio",
aliases = {"edit", "reopen"}, sync = true, origin = DirectorOrigin.PLAYER)
public void open(
@Param(description = "Pack dimension") IrisDimension dimension,
@Param(name = "key", aliases = {"structure", "name"},
description = "Existing key loaded from structures/<key>.json",
customHandler = IrisStructureHandler.class) String structure,
@Param(description = "Studio world seed", defaultValue = "1337") long seed
) {
CommandJigsawOpen.openProject(player(), sender(), dimension, structure, seed);
}
@Director(description = "Close the active Jigsaw Studio", sync = true,
origin = DirectorOrigin.PLAYER)
public void close(
@Param(description = "Discard unsaved Studio layout changes", defaultValue = "false") boolean discard
) {
Player targetPlayer = player();
ActiveContext context = active(targetPlayer);
if (context == null) {
sendError("You do not have an active Jigsaw Studio session.");
return;
}
if (context.session().isDirty() && !discard) {
sendError("This Jigsaw Studio is waiting for autosave. Wait for it to finish or run close with discard=true.");
return;
}
JigsawStudioService studioService = JigsawStudioService.get();
UUID requestId = context.request().requestId();
JigsawStudioService.CloseStart closeStart = studioService.tryBeginClose(
requestId,
targetPlayer.getUniqueId(),
discard);
if (closeStart != JigsawStudioService.CloseStart.STARTED) {
sendError(closeStart == JigsawStudioService.CloseStart.SAVE_IN_PROGRESS
? "This Jigsaw Studio is saving. Wait for the save to finish before closing it."
: closeStart == JigsawStudioService.CloseStart.OPERATION_IN_PROGRESS
? "This Jigsaw Studio is loading a variant. Wait for it to finish before closing it."
: closeStart == JigsawStudioService.CloseStart.DIRTY
? "This Jigsaw Studio is waiting for autosave. Wait for it to finish or run close with discard=true."
: closeStart == JigsawStudioService.CloseStart.NOT_OWNER
? "This Jigsaw Studio is owned by another player session."
: "This Jigsaw Studio session is no longer active.");
return;
}
VolmitSender commandSender = sender();
try {
Iris.service(StudioSVC.class).close().whenComplete((result, throwable) -> J.s(() -> {
if (throwable != null || result == null || result.failureCause() != null) {
studioService.cancelClose(requestId);
Throwable failure = throwable != null ? throwable : result == null ? null : result.failureCause();
commandSender.sendMessage(C.RED + "Jigsaw Studio close failed: "
+ (failure == null ? "unknown failure" : failure.getMessage()));
return;
}
JigsawStudioActivation.deactivate(context.packKey(), requestId);
PLAYER_PACKS.remove(targetPlayer.getUniqueId(),
new SessionBinding(context.packKey(), requestId));
commandSender.sendMessage(C.GREEN + "Jigsaw Studio closed.");
}));
} catch (Throwable exception) {
studioService.cancelClose(requestId);
Iris.reportError("Failed to close Jigsaw Studio '" + context.request().structureKey() + "'.", exception);
sendError("Jigsaw Studio close failed: " + exception.getMessage());
}
}
@Director(name = "delete", aliases = "remove", description = "Delete the active owned jigsaw project",
sync = true, origin = DirectorOrigin.PLAYER)
public void delete(
@Param(description = "Confirm permanent project deletion", defaultValue = "false") boolean confirm
) {
if (requireActive() == null) {
return;
}
if (!confirm) {
sendError("Project deletion removes every resource owned by this jigsaw. Run again with confirm=true.");
return;
}
JigsawStudioService.get().deleteProject(player());
}
@Director(description = "Show active Jigsaw Studio and dynamic evaluation state", sync = true,
origin = DirectorOrigin.PLAYER)
public void status() {
ActiveContext context = requireActive();
if (context == null) {
return;
}
JigsawStudioLayout layout = context.session().layout();
String selection = context.session().selectedBayId().orElse("none");
JigsawStudioVariant activeVariant = "none".equals(selection)
? null
: context.session().activeVariant(selection).orElse(null);
JigsawStudioBay selectedBay = "none".equals(selection) ? null : layout.get(selection);
JigsawStudioCellDimensions selectedDimensions = selectedBay == null
? null
: selectedBay.bounds().dimensions();
Optional<JigsawStudioGraphEvaluation> evaluation = JigsawStudioService.get().evaluation(player());
sender().sendMessage(C.AQUA + "Jigsaw Studio: " + context.request().structureKey());
sender().sendMessage(C.GRAY + "mode=" + layout.mode()
+ ", compatibility=" + context.request().compatibilityTarget()
+ ", defaultCell=" + layout.cellDimensions().width() + "x"
+ layout.cellDimensions().height() + "x" + layout.cellDimensions().depth()
+ ", workcells=" + layout.bays().size()
+ ", variants=" + layout.variantCatalog().size());
sender().sendMessage(C.GRAY + "selected=" + selection
+ ", activeVariant=" + (activeVariant == null ? "none" : activeVariant.pieceKey())
+ ", size=" + (selectedDimensions == null ? "none" : selectedDimensions.width() + "x"
+ selectedDimensions.height() + "x" + selectedDimensions.depth())
+ ", enabled=" + (selectedBay == null ? "n/a" : selectedBay.enabled())
+ ", autosavePending=" + context.session().isDirty());
if (evaluation.isPresent()) {
JigsawStudioGraphEvaluation current = evaluation.get();
sender().sendMessage(C.GRAY + "evaluation=" + current.state()
+ ", seed=" + current.seed()
+ ", theme=" + (current.selectedTheme().isBlank() ? "unthemed" : current.selectedTheme())
+ ", pieces=" + current.pieceCount()
+ ", detail=" + current.detail());
} else {
sender().sendMessage(C.GRAY + "evaluation=PENDING, seed=" + DEFAULT_STUDIO_SEED);
}
}
@Director(description = "Open the Jigsaw Studio workcell and variant menu", sync = true,
origin = DirectorOrigin.PLAYER)
public void menu() {
if (requireActive() != null) {
JigsawStudioService.get().openControlMenu(player());
}
}
@Director(description = "Select the Jigsaw Studio workcell containing you", sync = true,
origin = DirectorOrigin.PLAYER)
public void select() {
ActiveContext context = requireActive();
if (context == null) {
return;
}
Location location = player().getLocation();
JigsawStudioBay bay = context.session().layout().findAt(
location.getBlockX(), location.getBlockY(), location.getBlockZ());
if (bay == null) {
sendError("Stand inside a Jigsaw Studio workcell before selecting it.");
return;
}
context.session().selectBay(bay.stableId());
sender().sendMessage(C.GREEN + "Selected " + bay.stableId() + " (" + bay.kind() + ").");
}
@Director(name = "bounds", aliases = {"cell", "resize"},
description = "Set the selected Studio workcell capacity",
sync = true, origin = DirectorOrigin.PLAYER)
public void bounds(
@Param(description = "Cell width") int width,
@Param(description = "Cell height") int height,
@Param(description = "Cell depth") int depth
) {
ActiveContext context = requireActive();
if (context == null) {
return;
}
JigsawStudioBay selected = selectedBay(context);
if (selected == null) {
sendError("Select a Jigsaw Studio workcell before changing its capacity.");
return;
}
JigsawStudioCellDimensions dimensions;
try {
dimensions = new JigsawStudioCellDimensions(width, height, depth);
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
if (dimensions.equals(selected.capacity())) {
sender().sendMessage(C.YELLOW + "Workcell capacity is unchanged.");
return;
}
JigsawStudioService.get().updateWorkcellDimensions(player(), selected.stableId(), dimensions);
}
@Director(description = "Flush the automatic save for a workcell now", sync = true,
origin = DirectorOrigin.PLAYER)
public void save(
@Param(description = "Workcell ID or selected", defaultValue = "selected") String bay
) {
ActiveContext context = requireActive();
if (context == null) {
return;
}
if ("selected".equalsIgnoreCase(bay)) {
JigsawStudioBay selected = selectedBay(context);
if (selected == null) {
sendError("Select a Jigsaw Studio workcell before flushing its autosave.");
return;
}
JigsawStudioService.get().flushAutosave(player(), selected.stableId());
return;
}
JigsawStudioService.get().flushAutosave(player(), bay);
}
@Director(name = "goto", aliases = "teleport", description = "Select and teleport to a Studio workcell",
sync = true, origin = DirectorOrigin.PLAYER)
public void gotoBay(
@Param(description = "Workcell ID") String bay
) {
if (requireActive() != null) {
JigsawStudioService.get().teleportTo(player(), bay);
}
}
@Director(description = "Enable or disable Jigsaw Studio particles", sync = true,
origin = DirectorOrigin.PLAYER)
public void particles(
@Param(description = "Whether particles are visible") boolean visible
) {
if (requireActive() != null) {
JigsawStudioService.get().setParticles(player(), visible);
}
}
@Director(description = "Export the clean active graph as a vanilla datapack", sync = true,
origin = DirectorOrigin.PLAYER)
public void export(
@Param(description = "Datapack namespace", defaultValue = "iris") String namespace,
@Param(description = "Output name under the Studio exports folder", defaultValue = "jigsaw-export")
String output,
@Param(description = "directory or zip", defaultValue = "zip") String format,
@Param(description = "Replace an existing export", defaultValue = "false") boolean replace
) {
ActiveContext context = requireActive();
if (context == null) {
return;
}
if (context.session().isDirty()) {
sendError("Wait for the pending autosave or discard the edits before exporting the on-disk graph.");
return;
}
VanillaJigsawExportFormat exportFormat;
try {
exportFormat = CommandJigsawExports.parseFormat(format);
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
Path exportRoot = new File(Iris.service(StudioSVC.class).getWorkspaceFolder(), "exports")
.toPath().toAbsolutePath().normalize();
Path destination;
try {
destination = CommandJigsawExports.resolveExportDestination(exportRoot, output, exportFormat);
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
VanillaJigsawExportRequest request;
try {
request = VanillaJigsawExportRequest.builder(
VanillaJigsawExportSource.forData(
context.request().source(), context.request().structureKey()), destination)
.namespace(namespace)
.resourcePath(context.request().structureKey())
.format(exportFormat)
.replaceExisting(replace)
.build();
} catch (IllegalArgumentException exception) {
sendError(exception.getMessage());
return;
}
Player targetPlayer = player();
UUID playerId = targetPlayer.getUniqueId();
UUID requestId = context.request().requestId();
JigsawStudioService studioService = JigsawStudioService.get();
JigsawStudioService.ExportStart exportStart = studioService.tryBeginExport(requestId, playerId);
if (exportStart != JigsawStudioService.ExportStart.STARTED) {
sendError(CommandJigsawExports.exportStartError(exportStart));
return;
}
if (!CommandJigsawExports.beginExport(playerId, destination)) {
studioService.finishExport(requestId);
sendError("A vanilla Jigsaw export for this player or output is already in progress.");
return;
}
CommandJigsawExports.ExportLease lease = new CommandJigsawExports.ExportLease(() -> {
CommandJigsawExports.finishExport(playerId, destination);
studioService.finishExport(requestId);
});
CommandJigsawExports.ExportOperation operation = new CommandJigsawExports.ExportOperation(
targetPlayer,
sender(),
requestId,
context.request().structureKey(),
request,
lease);
sender().sendMessage(C.GRAY + "Exporting '" + operation.structureKey()
+ "' in the background to " + destination + "...");
try {
J.a(() -> CommandJigsawExports.runExport(operation));
} catch (RuntimeException exception) {
operation.lease().release();
Iris.reportError("Failed to schedule vanilla Jigsaw export for '"
+ operation.structureKey() + "'.", exception);
sendError("Vanilla export could not be scheduled: " + exception.getMessage());
}
}
static boolean studioIsActiveOrOpening() {
return JigsawStudioActivation.activeOwnerId() != null
|| JigsawStudioActivation.openingOwnerId() != null;
}
static ActiveContext active(Player player) {
if (player == null) {
return null;
}
SessionBinding binding = PLAYER_PACKS.get(player.getUniqueId());
if (binding == null) {
return null;
}
ActiveContext context = context(binding);
if (context == null) {
PLAYER_PACKS.remove(player.getUniqueId(), binding);
return null;
}
return context;
}
private static ActiveContext context(SessionBinding binding) {
JigsawStudioActivation.Request request = JigsawStudioActivation.getRequest(binding.packKey());
JigsawStudioSession session = JigsawStudioActivation.getSession(binding.packKey());
if (request == null || session == null
|| !binding.requestId().equals(request.requestId())
|| !request.requestId().equals(session.sessionId())) {
return null;
}
return new ActiveContext(binding.packKey(), request, session);
}
static ActiveContext findDirtyContext() {
for (SessionBinding binding : PLAYER_PACKS.values()) {
JigsawStudioActivation.Request request = JigsawStudioActivation.getRequest(binding.packKey());
JigsawStudioSession session = JigsawStudioActivation.getSession(binding.packKey());
if (request != null && session != null
&& binding.requestId().equals(request.requestId())
&& session.isDirty()) {
return new ActiveContext(binding.packKey(), request, session);
}
}
return null;
}
private ActiveContext requireActive() {
ActiveContext context = active(player());
if (context == null) {
sendError("Open a Jigsaw Studio project first.");
}
return context;
}
private IrisData requireData(IrisDimension dimension) {
return requireData(dimension, sender());
}
static IrisData requireData(IrisDimension dimension, VolmitSender commandSender) {
IrisData data = dimension == null ? null : dimension.getLoader();
if (data == null) {
sendError(commandSender, "Could not resolve the requested Iris pack dimension.");
}
return data;
}
private void sendError(String message) {
sender().sendMessage(C.RED + message);
}
static void sendError(VolmitSender commandSender, String message) {
commandSender.sendMessage(C.RED + message);
}
private static JigsawStudioMode parseMode(String value) {
return switch (value.trim().toLowerCase(Locale.ROOT)) {
case "planar", "2d", "planar_jigsaw" -> JigsawStudioMode.PLANAR_JIGSAW;
case "spatial", "3d", "spatial_jigsaw" -> JigsawStudioMode.SPATIAL_JIGSAW;
default -> throw new IllegalArgumentException("Mode must be planar or spatial.");
};
}
private static JigsawStudioCompatibilityTarget parseCompatibility(String value) {
return switch (value.trim().toLowerCase(Locale.ROOT)) {
case "extended", "iris", "iris_extended" -> JigsawStudioCompatibilityTarget.IRIS_EXTENDED;
case "vanilla", "portable", "vanilla_portable" -> JigsawStudioCompatibilityTarget.VANILLA_PORTABLE;
default -> throw new IllegalArgumentException("Compatibility must be iris or vanilla.");
};
}
static JigsawStudioCompatibilityTarget compatibilityOf(IrisStructure structure) {
return structure.resolvedCompatibility() == IrisJigsawCompatibility.VANILLA_PORTABLE
? JigsawStudioCompatibilityTarget.VANILLA_PORTABLE
: JigsawStudioCompatibilityTarget.IRIS_EXTENDED;
}
static NamespacedKey parseRegisteredStructureKey(String value) {
String normalized = Objects.requireNonNull(value, "Registered structure key").trim()
.toLowerCase(Locale.ROOT);
NamespacedKey key = NamespacedKey.fromString(normalized);
if (key == null) {
throw new IllegalArgumentException(
"Registered structure source must use a valid namespace:path key.");
}
return key;
}
static String resolveConversionTarget(NamespacedKey source, String value) {
Objects.requireNonNull(source, "Registered structure key");
String normalized = Objects.requireNonNull(value, "Conversion target").trim()
.toLowerCase(Locale.ROOT);
if (normalized.isEmpty() || "auto".equals(normalized)) {
return StructureImporter.deriveName(source);
}
StructureKey target = StructureKey.parse(normalized, "iris");
if (!"iris".equals(target.namespace())) {
throw new IllegalArgumentException("Converted Studio targets must use the iris namespace.");
}
return target.path();
}
static String conversionFailureMessage(VillageImporter.Result result) {
String message = Objects.requireNonNull(result, "Village import result").message();
String obsoleteInstruction = "; use 'import' for single-template structures";
int instructionIndex = message.indexOf(obsoleteInstruction);
if (instructionIndex < 0) {
return message;
}
return message.substring(0, instructionIndex)
+ ". This Studio converter accepts registered jigsaw structures only.";
}
static JigsawStudioBay selectedBay(ActiveContext context) {
String selected = context.session().selectedBayId().orElse(null);
return selected == null ? null : context.session().layout().get(selected);
}
static JigsawStudioVariant selectedVariant(ActiveContext context) {
JigsawStudioBay selected = selectedBay(context);
return selected == null
? null
: context.session().activeVariant(selected.stableId()).orElse(null);
}
static boolean runGraphMutation(
Player player,
ActiveContext context,
JigsawStudioService.CommandGraphMutation mutation
) {
return JigsawStudioService.get().runCommandGraphMutation(
player,
context.request().requestId(),
mutation);
}
static JigsawStudioLayout loadPersistedLayout(ActiveContext context) throws IOException {
IrisData source = context.request().source();
source.invalidateStructureResources();
IrisStructure structure = source.load(IrisStructure.class, context.request().structureKey(), false);
if (structure == null) {
throw new IOException("The structure could not be reloaded after the graph transaction");
}
return JigsawStudioGraphMapper.map(source, structure);
}
static JigsawStudioService.CommandGraphMutationResult mappedMutationResult(
ActiveContext context,
String activateWorkcellId,
String activatePieceKey,
String message
) throws IOException {
return new JigsawStudioService.CommandGraphMutationResult(
loadPersistedLayout(context),
activateWorkcellId,
activatePieceKey,
message);
}
static String workcellIdForVariant(JigsawStudioLayout layout, String pieceKey) throws IOException {
JigsawStudioVariant variant = layout.variantCatalog().find(pieceKey).orElse(null);
if (variant == null) {
throw new IOException("Persisted variant '" + pieceKey + "' did not map to a Studio workcell");
}
return variant.archetype()
.map(archetype -> archetype.stableId())
.orElse(JigsawStudioLayout.SPATIAL_WORKCELL_ID);
}
static PieceWorkcellResolution resolvePieceWorkcell(
JigsawStudioLayout layout,
IrisJigsawPiece piece,
IrisObject object
) {
JigsawStudioLayout activeLayout = Objects.requireNonNull(layout, "Jigsaw Studio layout");
IrisJigsawPiece activePiece = Objects.requireNonNull(piece, "Jigsaw piece");
IrisObject activeObject = Objects.requireNonNull(object, "Jigsaw piece object");
String workcellId;
IrisPosition requiredDimensions;
if (activeLayout.mode() == JigsawStudioMode.PLANAR_JIGSAW) {
IrisJigsawWorkcellArchetype modelArchetype = IrisJigsawWorkcellArchetype.fromPiece(activePiece);
workcellId = JigsawPlanarArchetype.fromModel(modelArchetype).stableId();
requiredDimensions = PlanarJigsawWorkcellResolver.canonicalDimensions(activePiece, activeObject);
} else {
workcellId = JigsawStudioLayout.SPATIAL_WORKCELL_ID;
requiredDimensions = new IrisPosition(
activeObject.getW(),
activeObject.getH(),
activeObject.getD());
}
JigsawStudioBay workcell = activeLayout.get(workcellId);
if (workcell == null) {
throw new IllegalArgumentException("Workcell '" + workcellId + "' is not configured");
}
return new PieceWorkcellResolution(workcell, requiredDimensions);
}
static String nextThemeKey(ActiveContext context) throws IOException {
IrisStructure structure = context.request().source().load(
IrisStructure.class,
context.request().structureKey(),
false);
if (structure == null) {
throw new IOException("The active jigsaw structure could not be loaded");
}
Set<String> declared = new HashSet<>();
if (structure.getThemeSets() != null) {
for (IrisJigsawThemeSet themeSet : structure.getThemeSets()) {
if (themeSet != null && themeSet.getKey() != null) {
declared.add(themeSet.getKey());
}
}
}
int candidate = 1;
while (candidate > 0) {
String key = "variant-" + candidate;
if (!declared.contains(key)) {
return key;
}
candidate = Math.incrementExact(candidate);
}
throw new IOException("Jigsaw Studio cannot allocate another numbered family key");
}
static void sendError(DirectorExecutor executor, String message) {
executor.sender().sendMessage(C.RED + message);
}
public record ActiveContext(
String packKey,
JigsawStudioActivation.Request request,
JigsawStudioSession session
) {
}
record PieceWorkcellResolution(
JigsawStudioBay workcell,
IrisPosition requiredDimensions
) {
PieceWorkcellResolution {
workcell = Objects.requireNonNull(workcell, "Jigsaw Studio workcell");
requiredDimensions = Objects.requireNonNull(requiredDimensions, "Jigsaw piece dimensions");
}
boolean fits() {
JigsawStudioCellDimensions capacity = workcell.capacity();
return requiredDimensions.getX() <= capacity.width()
&& requiredDimensions.getY() <= capacity.height()
&& requiredDimensions.getZ() <= capacity.depth();
}
}
record SessionBinding(String packKey, UUID requestId) {
}
public static final class JigsawModeHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return new KList<>("planar", "spatial");
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String input, boolean force) throws DirectorParsingException {
try {
return parseMode(input) == JigsawStudioMode.PLANAR_JIGSAW ? "planar" : "spatial";
} catch (IllegalArgumentException exception) {
throw new DirectorParsingException(exception.getMessage());
}
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
public static final class JigsawCompatibilityHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return new KList<>("iris", "vanilla");
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String input, boolean force) throws DirectorParsingException {
try {
return parseCompatibility(input) == JigsawStudioCompatibilityTarget.IRIS_EXTENDED
? "iris" : "vanilla";
} catch (IllegalArgumentException exception) {
throw new DirectorParsingException(exception.getMessage());
}
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
}
@@ -0,0 +1,423 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.structure.authoring.StructureKey;
import art.arcane.iris.structure.authoring.StructureOwnershipManifest;
import art.arcane.iris.structure.authoring.StructureTransactionWriter;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionDiagnostic;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionInputKind;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionPlan;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionRequest;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionResult;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionService;
import art.arcane.iris.structure.conversion.IrisStructureAdoptionStrategy;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.DirectorParameterHandler;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Director(name = "adopt", description = "Inspect and apply safe ownership adoption plans",
origin = DirectorOrigin.PLAYER)
public class CommandJigsawAdopt implements DirectorExecutor {
private static final Map<UUID, AdoptionPlanBinding> ADOPTION_PLANS = new ConcurrentHashMap<>();
private static final Map<Path, IrisStructureAdoptionService> ADOPTION_SERVICES = new ConcurrentHashMap<>();
@Director(description = "Inspect an Iris graph for safe Studio ownership adoption", sync = true,
origin = DirectorOrigin.PLAYER)
public void inspect(
@Param(description = "Pack dimension") IrisDimension dimension,
@Param(description = "Existing Iris structure key") String source,
@Param(description = "New owned Iris key, or auto", defaultValue = "auto") String target,
@Param(description = "auto, in-place, or clone", defaultValue = "auto",
customHandler = JigsawAdoptionStrategyHandler.class) String strategy
) {
VolmitSender commandSender = sender();
IrisData data = CommandJigsaw.requireData(dimension, commandSender);
if (data == null) {
return;
}
String sourceKey;
Optional<StructureKey> targetKey;
IrisStructureAdoptionStrategy adoptionStrategy;
Path root;
try {
sourceKey = parseAdoptionSource(source);
targetKey = parseAdoptionTarget(target);
adoptionStrategy = parseAdoptionStrategy(strategy);
root = canonicalAdoptionRoot(data.getDataFolder().toPath());
} catch (IOException | IllegalArgumentException exception) {
CommandJigsaw.sendError(commandSender, "Could not inspect adoption source: " + exception.getMessage());
return;
}
Player targetPlayer = player();
UUID ownerId = targetPlayer.getUniqueId();
commandSender.sendMessage(C.GRAY + "Inspecting Jigsaw adoption source '" + sourceKey
+ "' asynchronously...");
try {
J.a(() -> runAdoptionInspect(
targetPlayer,
commandSender,
ownerId,
dimension,
data,
root,
sourceKey,
targetKey,
adoptionStrategy));
} catch (RuntimeException exception) {
Iris.reportError("Failed to schedule Jigsaw adoption inspection for '"
+ sourceKey + "'.", exception);
CommandJigsaw.sendError(commandSender, "Jigsaw adoption inspection could not be scheduled: "
+ exception.getMessage());
}
}
@Director(description = "Apply one inspected adoption plan without overwriting", sync = true,
origin = DirectorOrigin.PLAYER)
public void apply(
@Param(description = "Plan UUID returned by adopt inspect",
customHandler = JigsawAdoptionPlanHandler.class) String planId
) {
VolmitSender commandSender = sender();
UUID parsedPlanId;
try {
parsedPlanId = UUID.fromString(planId);
} catch (IllegalArgumentException exception) {
CommandJigsaw.sendError(commandSender, "Adoption plan ID must be a UUID.");
return;
}
if (CommandJigsaw.studioIsActiveOrOpening()) {
CommandJigsaw.sendError(commandSender,
"Close the active or opening Jigsaw Studio before applying an adoption plan.");
return;
}
pruneAdoptionPlanBindings();
AdoptionPlanBinding binding = ADOPTION_PLANS.get(parsedPlanId);
UUID playerId = player().getUniqueId();
if (binding == null || !binding.ownerId().equals(playerId)) {
CommandJigsaw.sendError(commandSender, "No active adoption plan '" + parsedPlanId
+ "' belongs to your player session. Run adopt inspect again.");
return;
}
if (binding.service().plan(parsedPlanId).isEmpty()) {
ADOPTION_PLANS.remove(parsedPlanId, binding);
CommandJigsaw.sendError(commandSender, "Adoption plan '" + parsedPlanId
+ "' expired or was already consumed. Run adopt inspect again.");
return;
}
if (!ADOPTION_PLANS.remove(parsedPlanId, binding)) {
CommandJigsaw.sendError(commandSender, "Adoption plan '" + parsedPlanId + "' is already being applied.");
return;
}
Player targetPlayer = player();
commandSender.sendMessage(C.GRAY + "Applying adoption plan '" + parsedPlanId
+ "' asynchronously without overwrite...");
try {
J.a(() -> runAdoptionApply(targetPlayer, commandSender, parsedPlanId, binding));
} catch (RuntimeException exception) {
ADOPTION_PLANS.putIfAbsent(parsedPlanId, binding);
Iris.reportError("Failed to schedule Jigsaw adoption plan '" + parsedPlanId + "'.", exception);
CommandJigsaw.sendError(commandSender, "Jigsaw adoption could not be scheduled: " + exception.getMessage());
}
}
private static void pruneAdoptionPlanBindings() {
for (Map.Entry<UUID, AdoptionPlanBinding> entry : ADOPTION_PLANS.entrySet()) {
AdoptionPlanBinding binding = entry.getValue();
if (binding.service().plan(entry.getKey()).isEmpty()) {
ADOPTION_PLANS.remove(entry.getKey(), binding);
}
}
}
private static void runAdoptionApply(
Player targetPlayer,
VolmitSender commandSender,
UUID planId,
AdoptionPlanBinding binding
) {
if (CommandJigsaw.studioIsActiveOrOpening()) {
ADOPTION_PLANS.putIfAbsent(planId, binding);
J.runEntity(targetPlayer, () -> CommandJigsaw.sendError(commandSender,
"Close the active or opening Jigsaw Studio before applying an adoption plan."));
return;
}
IrisStructureAdoptionResult result;
Throwable failure = null;
try {
result = binding.service().apply(binding.plan());
} catch (Throwable throwable) {
result = null;
failure = throwable;
Iris.reportError("Failed to apply Jigsaw adoption plan '" + planId + "'.", throwable);
}
IrisStructureAdoptionResult completedResult = result;
Throwable completedFailure = failure;
J.runEntity(targetPlayer, () -> finishAdoptionApply(
targetPlayer,
commandSender,
binding,
completedResult,
completedFailure));
}
private static void runAdoptionInspect(
Player targetPlayer,
VolmitSender commandSender,
UUID ownerId,
IrisDimension dimension,
IrisData data,
Path root,
String sourceKey,
Optional<StructureKey> targetKey,
IrisStructureAdoptionStrategy strategy
) {
IrisStructureAdoptionService service = null;
IrisStructureAdoptionPlan plan = null;
Throwable failure = null;
try {
IrisStructureAdoptionInputKind inputKind = adoptionInputKind(root, sourceKey);
service = ADOPTION_SERVICES.computeIfAbsent(root, IrisStructureAdoptionService::new);
plan = service.inspect(new IrisStructureAdoptionRequest(
sourceKey,
targetKey,
strategy,
inputKind));
} catch (Throwable throwable) {
failure = throwable;
Iris.reportError("Failed to inspect Jigsaw adoption source '" + sourceKey + "'.", throwable);
}
IrisStructureAdoptionService completedService = service;
IrisStructureAdoptionPlan completedPlan = plan;
Throwable completedFailure = failure;
J.runEntity(targetPlayer, () -> finishAdoptionInspect(
commandSender,
ownerId,
dimension,
data,
completedService,
completedPlan,
completedFailure));
}
private static void finishAdoptionInspect(
VolmitSender commandSender,
UUID ownerId,
IrisDimension dimension,
IrisData data,
IrisStructureAdoptionService service,
IrisStructureAdoptionPlan plan,
Throwable failure
) {
if (failure != null || service == null || plan == null) {
CommandJigsaw.sendError(commandSender, "Jigsaw adoption inspection failed: "
+ (failure == null ? "no result" : failure.getMessage()));
return;
}
pruneAdoptionPlanBindings();
if (service.plan(plan.planId()).isPresent()) {
ADOPTION_PLANS.put(plan.planId(), new AdoptionPlanBinding(
ownerId, dimension, data, service, plan));
}
reportAdoptionPlan(commandSender, plan);
}
private static void finishAdoptionApply(
Player targetPlayer,
VolmitSender commandSender,
AdoptionPlanBinding binding,
IrisStructureAdoptionResult result,
Throwable failure
) {
if (failure != null || result == null) {
CommandJigsaw.sendError(commandSender, "Jigsaw adoption failed: "
+ (failure == null ? "no result" : failure.getMessage()));
return;
}
reportAdoptionResult(commandSender, result);
if (!result.successful()) {
return;
}
StructureKey target = result.receipt().orElseThrow().targetStructure();
binding.data().invalidateStructureResources();
CommandJigsawOpen.openProject(targetPlayer, commandSender, binding.dimension(), target.path(), CommandJigsaw.DEFAULT_STUDIO_SEED);
}
private static void reportAdoptionPlan(VolmitSender commandSender, IrisStructureAdoptionPlan plan) {
C statusColor = plan.canApply() ? C.GREEN : C.RED;
commandSender.sendMessage(statusColor + "Adoption plan " + plan.planId() + " -> "
+ plan.disposition() + " for " + plan.targetStructure().value() + ".");
commandSender.sendMessage(C.GRAY + "Resources=" + plan.resourceCount()
+ ", bytes=" + plan.totalSourceBytes()
+ ", errors=" + plan.errorCount()
+ ", warnings=" + plan.warningCount() + ".");
for (IrisStructureAdoptionDiagnostic diagnostic : plan.diagnostics()) {
commandSender.sendMessage(adoptionColor(diagnostic).toString() + diagnostic.summary());
}
if (plan.canApply()) {
commandSender.sendMessage(C.AQUA + "Apply with /iris jigsaw adopt apply " + plan.planId());
}
}
private static void reportAdoptionResult(VolmitSender commandSender, IrisStructureAdoptionResult result) {
commandSender.sendMessage((result.successful() ? C.GREEN : C.RED)
+ "Adoption plan " + result.planId() + ": " + result.status() + ".");
for (IrisStructureAdoptionDiagnostic diagnostic : result.diagnostics()) {
commandSender.sendMessage(adoptionColor(diagnostic).toString() + diagnostic.summary());
}
}
private static C adoptionColor(IrisStructureAdoptionDiagnostic diagnostic) {
return switch (diagnostic.severity()) {
case ERROR -> C.RED;
case WARNING -> C.YELLOW;
case INFO -> C.GRAY;
};
}
static String parseAdoptionSource(String value) {
String normalized = Objects.requireNonNull(value, "Adoption source").trim()
.toLowerCase(Locale.ROOT);
StructureKey source = StructureKey.parse(normalized, "iris");
if (!"iris".equals(source.namespace())) {
throw new IllegalArgumentException("Adoption sources must use the iris namespace.");
}
return source.path();
}
static Optional<StructureKey> parseAdoptionTarget(String value) {
String normalized = Objects.requireNonNull(value, "Adoption target").trim()
.toLowerCase(Locale.ROOT);
if (normalized.isEmpty() || "auto".equals(normalized)) {
return Optional.empty();
}
StructureKey target = StructureKey.parse(normalized, "iris");
if (!"iris".equals(target.namespace())) {
throw new IllegalArgumentException("Adoption targets must use the iris namespace.");
}
return Optional.of(target);
}
private static IrisStructureAdoptionStrategy parseAdoptionStrategy(String value) {
return switch (Objects.requireNonNull(value, "Adoption strategy").trim().toLowerCase(Locale.ROOT)) {
case "auto" -> IrisStructureAdoptionStrategy.AUTO;
case "in-place", "in_place", "inplace", "claim" -> IrisStructureAdoptionStrategy.IN_PLACE;
case "clone", "copy" -> IrisStructureAdoptionStrategy.CLONE;
default -> throw new IllegalArgumentException("Adoption strategy must be auto, in-place, or clone.");
};
}
static IrisStructureAdoptionInputKind adoptionInputKind(Path packRoot, String source) throws IOException {
Path root = canonicalAdoptionRoot(packRoot);
StructureKey sourceKey = new StructureKey("iris", parseAdoptionSource(source));
Path manifestPath = new StructureTransactionWriter(root).ownershipManifestPath(sourceKey);
if (!Files.exists(manifestPath, LinkOption.NOFOLLOW_LINKS)) {
return IrisStructureAdoptionInputKind.UNOWNED_IRIS;
}
if (!Files.isRegularFile(manifestPath, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Ownership manifest is not a regular file: " + manifestPath);
}
StructureOwnershipManifest manifest;
try {
manifest = StructureOwnershipManifest.fromJson(Files.readAllBytes(manifestPath));
} catch (RuntimeException exception) {
throw new IOException("Invalid ownership manifest at " + manifestPath, exception);
}
if (!manifest.structure().equals(sourceKey)) {
throw new IOException("Ownership manifest at " + manifestPath
+ " belongs to " + manifest.structure().value());
}
return manifest.provenance().origin() == StructureOwnershipManifest.Origin.MANAGED_DATAPACK
? IrisStructureAdoptionInputKind.MANAGED_DATAPACK
: IrisStructureAdoptionInputKind.UNOWNED_IRIS;
}
private static Path canonicalAdoptionRoot(Path packRoot) throws IOException {
Path normalized = Objects.requireNonNull(packRoot, "Adoption pack root").toAbsolutePath().normalize();
return Files.exists(normalized, LinkOption.NOFOLLOW_LINKS) ? normalized.toRealPath() : normalized;
}
private record AdoptionPlanBinding(
UUID ownerId,
IrisDimension dimension,
IrisData data,
IrisStructureAdoptionService service,
IrisStructureAdoptionPlan plan
) {
}
public static final class JigsawAdoptionStrategyHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return new KList<>("auto", "in-place", "clone");
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String input, boolean force) throws DirectorParsingException {
try {
return switch (parseAdoptionStrategy(input)) {
case AUTO -> "auto";
case IN_PLACE -> "in-place";
case CLONE -> "clone";
};
} catch (IllegalArgumentException exception) {
throw new DirectorParsingException(exception.getMessage());
}
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
public static final class JigsawAdoptionPlanHandler implements DirectorParameterHandler<String> {
@Override
public KList<String> getPossibilities() {
return new KList<>();
}
@Override
public String toString(String value) {
return value == null ? "" : value;
}
@Override
public String parse(String input, boolean force) throws DirectorParsingException {
try {
return UUID.fromString(input).toString();
} catch (IllegalArgumentException exception) {
throw new DirectorParsingException("Adoption plan ID must be a UUID.");
}
}
@Override
public boolean supports(Class<?> type) {
return type == String.class;
}
}
}
@@ -0,0 +1,83 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphEditor;
import art.arcane.iris.studio.jigsaw.JigsawStudioVariant;
import art.arcane.iris.structure.object.IrisObject;
import art.arcane.iris.pack.value.IrisPosition;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Jigsaw;
@Director(name = "connector", description = "Inspect and configure targeted jigsaw connectors",
origin = DirectorOrigin.PLAYER)
public class CommandJigsawConnector implements DirectorExecutor {
@Director(description = "Set the Iris-only channel on the targeted jigsaw marker", sync = true,
origin = DirectorOrigin.PLAYER)
public void channel(
@Param(description = "Exact channel, or none to clear") String channel
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
Block target = player().getTargetBlockExact(8);
if (target == null || !(target.getBlockData() instanceof Jigsaw)) {
CommandJigsaw.sendError(this, "Look directly at a jigsaw marker within eight blocks.");
return;
}
JigsawStudioBay bay = context.session().layout().findAt(
target.getX(), target.getY(), target.getZ());
JigsawStudioVariant activeVariant = bay == null
? null
: context.session().activeVariant(bay.stableId()).orElse(null);
if (activeVariant == null) {
CommandJigsaw.sendError(this, "The targeted marker must be inside a workcell with an active variant.");
return;
}
if (!activeVariant.owned()) {
CommandJigsaw.sendError(this, "The active variant is read-only. Adopt it into this project before editing it.");
return;
}
IrisObject object = context.request().source().getObjectLoader().load(activeVariant.objectKey());
if (object == null) {
CommandJigsaw.sendError(this, "The active variant object '" + activeVariant.objectKey() + "' is missing.");
return;
}
IrisPosition canonicalPosition = new IrisPosition(
target.getX() - bay.bounds().originX(),
target.getY() - bay.bounds().originY(),
target.getZ() - bay.bounds().originZ());
IrisPosition position;
try {
position = activeVariant.canonicalToSourcePosition(
canonicalPosition,
new JigsawStudioCellDimensions(object.getW(), object.getH(), object.getD()));
} catch (IllegalArgumentException exception) {
CommandJigsaw.sendError(this, "Connector channel update failed: " + exception.getMessage());
return;
}
String normalized = channel == null || channel.isBlank() || "none".equalsIgnoreCase(channel)
? "none"
: channel.trim();
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioGraphEditor.updateConnectorChannel(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
activeVariant.pieceKey(),
position,
channel);
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Persisted connector channel '" + normalized + "' on piece '"
+ activeVariant.pieceKey() + "' at " + position.getX() + "," + position.getY()
+ "," + position.getZ() + ".");
});
}
}
@@ -0,0 +1,159 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.iris.structure.export.VanillaJigsawDatapackExporter;
import art.arcane.iris.structure.export.VanillaJigsawExportDiagnostic;
import art.arcane.iris.structure.export.VanillaJigsawExportFormat;
import art.arcane.iris.structure.export.VanillaJigsawExportRequest;
import art.arcane.iris.structure.export.VanillaJigsawExportResult;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import org.bukkit.entity.Player;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
final class CommandJigsawExports {
private static final Pattern EXPORT_OUTPUT_NAME = Pattern.compile("[a-zA-Z0-9_-][a-zA-Z0-9._-]{0,127}");
private static final Set<UUID> EXPORTING_PLAYERS = ConcurrentHashMap.newKeySet();
private static final Set<Path> EXPORT_DESTINATIONS = ConcurrentHashMap.newKeySet();
private CommandJigsawExports() {
}
static String exportStartError(JigsawStudioService.ExportStart exportStart) {
Objects.requireNonNull(exportStart, "Jigsaw Studio export start status");
return switch (exportStart) {
case NOT_ACTIVE -> "The active Jigsaw Studio is no longer available.";
case NOT_OWNER -> "Only the Jigsaw Studio owner can export this project.";
case DIRTY -> "Wait for the pending autosave or discard the edits before exporting the on-disk graph.";
case CLOSING -> "The active Jigsaw Studio is closing and cannot be exported.";
case SAVE_IN_PROGRESS -> "Wait for the current Jigsaw Studio save to finish before exporting.";
case OPERATION_IN_PROGRESS -> "Wait for the current Jigsaw Studio operation to finish before exporting.";
case IN_PROGRESS -> "A Jigsaw Studio export is already in progress.";
case STARTED -> throw new IllegalArgumentException("STARTED is not an export failure");
};
}
static boolean beginExport(UUID playerId, Path destination) {
Objects.requireNonNull(playerId);
Path normalizedDestination = Objects.requireNonNull(destination).toAbsolutePath().normalize();
if (!EXPORTING_PLAYERS.add(playerId)) {
return false;
}
if (EXPORT_DESTINATIONS.add(normalizedDestination)) {
return true;
}
EXPORTING_PLAYERS.remove(playerId);
return false;
}
static void finishExport(UUID playerId, Path destination) {
EXPORTING_PLAYERS.remove(Objects.requireNonNull(playerId));
EXPORT_DESTINATIONS.remove(Objects.requireNonNull(destination).toAbsolutePath().normalize());
}
static void runExport(ExportOperation operation) {
VanillaJigsawExportResult result = null;
Throwable failure = null;
try {
result = new VanillaJigsawDatapackExporter().export(operation.request());
} catch (Throwable throwable) {
failure = throwable;
Iris.reportError("Vanilla Jigsaw export failed for '" + operation.structureKey() + "'.", throwable);
} finally {
operation.lease().release();
}
VanillaJigsawExportResult completedResult = result;
Throwable completedFailure = failure;
Runnable response = () -> reportExport(operation, completedResult, completedFailure);
J.runEntity(operation.player(), response);
}
private static void reportExport(
ExportOperation operation,
VanillaJigsawExportResult result,
Throwable failure
) {
if (failure != null || result == null) {
operation.sender().sendMessage(C.RED + "Background vanilla export for '"
+ operation.structureKey() + "' failed: "
+ (failure == null ? "no result" : failure.getMessage()));
return;
}
for (VanillaJigsawExportDiagnostic diagnostic : result.diagnostics()) {
C color = diagnostic.isBlocking() ? C.RED : C.YELLOW;
operation.sender().sendMessage(color.toString() + diagnostic.code() + ": " + diagnostic.message());
}
CommandJigsaw.ActiveContext current = CommandJigsaw.active(operation.player());
boolean sameSession = current != null
&& current.request().requestId().equals(operation.requestId());
String contextLabel = sameSession ? "Vanilla export" : "Background vanilla export for '"
+ operation.structureKey() + "'";
operation.sender().sendMessage((result.isSuccess() ? C.GREEN : C.RED) + contextLabel + " "
+ result.status() + ": " + result.output());
}
static VanillaJigsawExportFormat parseFormat(String value) {
return switch (value.trim().toLowerCase(Locale.ROOT)) {
case "zip" -> VanillaJigsawExportFormat.ZIP;
case "directory", "dir", "folder" -> VanillaJigsawExportFormat.DIRECTORY;
default -> throw new IllegalArgumentException("Export format must be directory or zip.");
};
}
static Path resolveExportDestination(
Path exportRoot,
String output,
VanillaJigsawExportFormat format
) {
Path root = Objects.requireNonNull(exportRoot, "Jigsaw export root").toAbsolutePath().normalize();
String requested = Objects.requireNonNull(output, "Jigsaw export output name");
String name = requested.trim();
if (!name.equals(requested) || !EXPORT_OUTPUT_NAME.matcher(name).matches()) {
throw new IllegalArgumentException(
"Export output must be one file name using letters, numbers, '.', '_', or '-'.");
}
String fileName = format == VanillaJigsawExportFormat.ZIP && !name.endsWith(".zip")
? name + ".zip" : name;
Path destination = root.resolve(fileName).normalize();
if (destination.equals(root) || !root.equals(destination.getParent())) {
throw new IllegalArgumentException("Export output must be one artifact inside the Studio exports folder.");
}
return destination;
}
record ExportOperation(
Player player,
VolmitSender sender,
UUID requestId,
String structureKey,
VanillaJigsawExportRequest request,
ExportLease lease
) {
}
static final class ExportLease {
private final Runnable release;
private final AtomicBoolean released;
ExportLease(Runnable release) {
this.release = Objects.requireNonNull(release, "Jigsaw export lease release action");
this.released = new AtomicBoolean();
}
void release() {
if (released.compareAndSet(false, true)) {
release.run();
}
}
}
}
@@ -0,0 +1,251 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.BrokenPackException;
import art.arcane.iris.world.runtime.WorldRuntimeControlService;
import art.arcane.iris.studio.object.StudioOpenCoordinator;
import art.arcane.iris.studio.jigsaw.JigsawStudioActivation;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphMapper;
import art.arcane.iris.studio.jigsaw.JigsawStudioLayout;
import art.arcane.iris.studio.jigsaw.JigsawStudioSession;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.placement.IrisStructure;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.concurrent.CompletionException;
final class CommandJigsawOpen {
private CommandJigsawOpen() {
}
static void openProject(
Player targetPlayer,
VolmitSender commandSender,
IrisDimension dimension,
String structure,
long seed
) {
UUID playerId = targetPlayer.getUniqueId();
UUID ownerId = JigsawStudioActivation.activeOwnerId();
if (ownerId != null && !ownerId.equals(playerId)) {
CommandJigsaw.sendError(commandSender, "Jigsaw Studio is already owned by another player session.");
return;
}
UUID openingOwner = JigsawStudioActivation.openingOwnerId();
if (openingOwner != null) {
CommandJigsaw.sendError(commandSender, openingOwner.equals(playerId)
? "Your Jigsaw Studio is already opening."
: "Jigsaw Studio is already opening for another player.");
return;
}
CommandJigsaw.ActiveContext previous = CommandJigsaw.active(targetPlayer);
if (ownerId != null && previous == null) {
CommandJigsaw.sendError(commandSender, "Your active Jigsaw Studio binding is unavailable. Close it before opening another project.");
return;
}
CommandJigsaw.ActiveContext dirty = CommandJigsaw.findDirtyContext();
if (dirty != null) {
CommandJigsaw.sendError(commandSender,
"The current Jigsaw Studio is waiting for autosave. Wait for it to finish or close with discard=true.");
return;
}
IrisData data = CommandJigsaw.requireData(dimension, commandSender);
if (data == null) {
return;
}
IrisStructure irisStructure = data.load(IrisStructure.class, structure, false);
if (irisStructure == null) {
CommandJigsaw.sendError(commandSender, "No Iris jigsaw structure '" + structure + "' exists in this pack.");
return;
}
JigsawStudioLayout layout;
try {
layout = JigsawStudioGraphMapper.map(data, irisStructure);
} catch (RuntimeException exception) {
Iris.reportError("Failed to map Jigsaw Studio graph '" + structure + "'.", exception);
CommandJigsaw.sendError(commandSender, "Could not map this jigsaw graph: " + exception.getMessage());
return;
}
String packKey = dimension.getLoadKey();
if (!JigsawStudioActivation.tryBeginOpen(playerId)) {
CommandJigsaw.sendError(commandSender,
"Jigsaw Studio began opening for another player. Try again after it finishes.");
return;
}
JigsawStudioService studioService = JigsawStudioService.get();
UUID closingRequestId = previous == null ? null : previous.request().requestId();
if (closingRequestId != null) {
JigsawStudioService.CloseStart closeStart = studioService.tryBeginClose(
closingRequestId,
playerId,
false);
if (closeStart != JigsawStudioService.CloseStart.STARTED) {
JigsawStudioActivation.finishOpen(playerId);
CommandJigsaw.sendError(commandSender, closeStart == JigsawStudioService.CloseStart.SAVE_IN_PROGRESS
? "The current Jigsaw Studio is saving. Wait for it to finish before opening another project."
: closeStart == JigsawStudioService.CloseStart.OPERATION_IN_PROGRESS
? "The current Jigsaw Studio is loading a variant. Wait for it to finish before opening another project."
: closeStart == JigsawStudioService.CloseStart.DIRTY
? "The current Jigsaw Studio is waiting for autosave. Wait for it to finish before opening another project."
: "The current Jigsaw Studio could not be reserved for replacement.");
return;
}
}
JigsawStudioActivation.StagedActivation staged;
try {
staged = JigsawStudioActivation.stage(
packKey,
structure,
layout.mode(),
CommandJigsaw.compatibilityOf(irisStructure),
layout.cellDimensions(),
data,
layout,
playerId,
closingRequestId);
} catch (RuntimeException exception) {
JigsawStudioActivation.finishOpen(playerId);
studioService.cancelClose(closingRequestId);
Iris.reportError("Failed to stage Jigsaw Studio for '" + structure + "'.", exception);
CommandJigsaw.sendError(commandSender, "Jigsaw Studio staging failed: " + exception.getMessage());
return;
}
commandSender.sendMessage(C.GREEN + "Opening transient Jigsaw Studio for '" + structure + "' with "
+ layout.bays().size() + " workcells.");
try {
Iris.service(StudioSVC.class).openTracked(
commandSender,
seed,
packKey,
CommandJigsaw.STUDIO_OPEN_KIND,
() -> beginStagedOpen(staged),
world -> finishOpen(targetPlayer, world, staged, studioService))
.whenComplete((result, throwable) -> J.s(() -> finishOpenAttempt(
commandSender,
playerId,
structure,
closingRequestId,
studioService,
staged,
result,
throwable)));
} catch (Throwable exception) {
JigsawStudioActivation.finishOpen(playerId);
studioService.cancelClose(closingRequestId);
JigsawStudioActivation.rollback(staged);
Iris.reportError("Failed to open Jigsaw Studio for '" + structure + "'.", exception);
CommandJigsaw.sendError(commandSender, "Jigsaw Studio open failed: " + exception.getMessage());
}
}
private static void beginStagedOpen(JigsawStudioActivation.StagedActivation staged) {
if (!JigsawStudioActivation.beginStagedGeneration(staged)) {
throw new IllegalStateException("The staged Jigsaw Studio activation is no longer current");
}
}
private static void finishOpenAttempt(
VolmitSender commandSender,
UUID playerId,
String structureKey,
UUID closingRequestId,
JigsawStudioService studioService,
JigsawStudioActivation.StagedActivation staged,
StudioOpenCoordinator.StudioOpenResult result,
Throwable throwable
) {
JigsawStudioActivation.finishOpen(playerId);
if (throwable == null && result != null) {
studioService.cancelClose(closingRequestId);
return;
}
studioService.cancelClose(closingRequestId);
JigsawStudioActivation.rollback(staged);
Throwable failure = unwrapCompletionFailure(throwable == null
? new IllegalStateException("Studio open completed without a result")
: throwable);
if (isExpectedOpenDenial(failure)) {
Iris.warn("Jigsaw Studio open for '%s' was denied: %s",
structureKey, failure.getMessage());
return;
}
Iris.reportError("Failed to open Jigsaw Studio for '" + structureKey + "'.", failure);
CommandJigsaw.sendError(commandSender, "Jigsaw Studio open failed: " + failure.getMessage());
}
private static void finishOpen(
Player targetPlayer,
World world,
JigsawStudioActivation.StagedActivation staged,
JigsawStudioService studioService
) {
if (world == null) {
throw new IllegalStateException("Jigsaw Studio opened without a world");
}
JigsawStudioActivation.Request request = staged.request();
JigsawStudioSession session = staged.session();
try {
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
} catch (Throwable exception) {
Iris.reportError("Failed to apply Jigsaw Studio world rules for '" + world.getName() + "'.", exception);
}
JigsawStudioBay destination = initialBay(session.layout());
Location target = destination == null
? new Location(world, 0.5D, 66D, 0.5D)
: new Location(
world,
destination.bounds().originX() + destination.bounds().dimensions().width() / 2.0D,
destination.bounds().maxY() + 2.0D,
destination.bounds().originZ() + destination.bounds().dimensions().depth() / 2.0D);
if (destination != null) {
session.selectBay(destination.stableId());
}
if (!JigsawStudioActivation.commit(staged)) {
throw new IllegalStateException("The staged Jigsaw Studio activation could not be committed");
}
studioService.activationCommitted(world, request.requestId());
CommandJigsaw.PLAYER_PACKS.put(targetPlayer.getUniqueId(), new CommandJigsaw.SessionBinding(
request.packKey(), request.requestId()));
try {
J.runEntity(targetPlayer, () -> BukkitPlatform.teleportAsync(targetPlayer, target)
.thenRun(() -> J.runEntity(targetPlayer, () -> targetPlayer.setGameMode(GameMode.CREATIVE))));
} catch (RuntimeException exception) {
Iris.reportError("Failed to move the player to the committed Jigsaw Studio workcell.", exception);
}
}
private static JigsawStudioBay initialBay(JigsawStudioLayout layout) {
for (JigsawStudioBay bay : layout.bays()) {
if (layout.defaultVariant(bay).isPresent()) {
return bay;
}
}
return layout.bays().isEmpty() ? null : layout.bays().getFirst();
}
private static Throwable unwrapCompletionFailure(Throwable failure) {
Throwable current = failure;
while (current instanceof CompletionException && current.getCause() != null) {
current = current.getCause();
}
return current;
}
static boolean isExpectedOpenDenial(Throwable failure) {
return unwrapCompletionFailure(failure) instanceof BrokenPackException;
}
}
@@ -0,0 +1,237 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawPlanarTopology;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.studio.jigsaw.JigsawStudioCompatibilityTarget;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphEditor;
import art.arcane.iris.studio.jigsaw.JigsawStudioLayout;
import art.arcane.iris.studio.jigsaw.JigsawStudioMode;
import art.arcane.iris.studio.jigsaw.JigsawStudioPoolEditor;
import art.arcane.iris.studio.jigsaw.JigsawStudioVariant;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.iris.structure.jigsaw.IrisJigsawPiece;
import art.arcane.iris.structure.object.IrisObject;
import art.arcane.iris.pack.value.IrisPosition;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import org.bukkit.Location;
import java.io.IOException;
@Director(name = "piece", description = "Manage contextual Studio pieces", origin = DirectorOrigin.PLAYER)
public class CommandJigsawPiece implements DirectorExecutor {
@Director(description = "Create an owned piece from the selected Studio topology", sync = true,
origin = DirectorOrigin.PLAYER)
public void create(
@Param(description = "Owned pool resource key") String poolKey,
@Param(description = "New owned piece and object key") String pieceKey,
@Param(description = "Positive selection weight", defaultValue = "1") int weight
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawPlanarTopology topology = null;
JigsawStudioBay targetWorkcell = context.session().layout().get(JigsawStudioLayout.SPATIAL_WORKCELL_ID);
if (context.session().layout().mode() == JigsawStudioMode.PLANAR_JIGSAW) {
Location location = player().getLocation();
JigsawStudioBay contextual = context.session().layout().findAt(
location.getBlockX(), location.getBlockY(), location.getBlockZ());
if (contextual == null) {
contextual = CommandJigsaw.selectedBay(context);
} else {
context.session().selectBay(contextual.stableId());
}
if (contextual == null || contextual.topology().isEmpty()) {
CommandJigsaw.sendError(this, "Stand in or select a planar topology workcell before creating a variant.");
return;
}
topology = contextual.topology().get();
targetWorkcell = contextual;
}
if (targetWorkcell == null) {
CommandJigsaw.sendError(this, "The selected Jigsaw Studio workcell is no longer available.");
return;
}
JigsawPlanarTopology selectedTopology = topology;
String activationWorkcellId = targetWorkcell.stableId();
JigsawStudioCellDimensions dimensions = targetWorkcell.capacity();
String topologyText = selectedTopology == null ? "blank spatial" : selectedTopology.name();
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioGraphEditor.createPiece(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
pieceKey,
weight,
dimensions,
selectedTopology);
return CommandJigsaw.mappedMutationResult(
context,
activationWorkcellId,
pieceKey,
"Created owned " + topologyText + " piece '" + pieceKey
+ "' and added it to pool '" + poolKey + "' atomically.");
});
}
@Director(description = "Add an existing jigsaw piece to an owned pool transactionally", sync = true,
origin = DirectorOrigin.PLAYER)
public void add(
@Param(description = "Owned pool resource key") String poolKey,
@Param(description = "Existing jigsaw piece key") String pieceKey,
@Param(description = "Positive selection weight", defaultValue = "1") int weight
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
IrisJigsawPiece piece = context.request().source().getJigsawPieceLoader().load(pieceKey);
if (piece == null) {
CommandJigsaw.sendError(this, "No jigsaw piece '" + pieceKey + "' exists in this pack.");
return;
}
if (piece.getObject() == null || piece.getObject().isBlank()) {
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' does not declare an object.");
return;
}
IrisObject object = context.request().source().getObjectLoader().load(piece.getObject());
if (object == null) {
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' has no loadable object.");
return;
}
CommandJigsaw.PieceWorkcellResolution resolution;
try {
resolution = CommandJigsaw.resolvePieceWorkcell(context.session().layout(), piece, object);
} catch (IllegalArgumentException exception) {
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' cannot be assigned to a Studio workcell: "
+ exception.getMessage());
return;
}
if (!resolution.fits()) {
IrisPosition required = resolution.requiredDimensions();
JigsawStudioCellDimensions capacity = resolution.workcell().capacity();
CommandJigsaw.sendError(this, "Jigsaw piece '" + pieceKey + "' requires " + required.getX() + "x"
+ required.getY() + "x" + required.getZ() + " in " + resolution.workcell().displayName()
+ ", but that workcell capacity is " + capacity.width() + "x" + capacity.height()
+ "x" + capacity.depth() + ".");
return;
}
String objectKey = piece.getObject();
CommandJigsaw.runGraphMutation(player(), context, () -> {
if (!JigsawStudioGraphEditor.ownsPiece(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
pieceKey,
objectKey)) {
throw new IOException("Piece add accepts only piece/object resources already owned by this project; "
+ "use piece create for a new owned piece");
}
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.addPiece(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
pieceKey,
weight);
if (!update.changed()) {
throw new IOException("The owned pool is missing or already references '" + pieceKey + "'");
}
JigsawStudioLayout layout = CommandJigsaw.loadPersistedLayout(context);
String activationWorkcellId = CommandJigsaw.workcellIdForVariant(layout, pieceKey);
return new JigsawStudioService.CommandGraphMutationResult(
layout,
activationWorkcellId,
pieceKey,
"Added '" + pieceKey + "' to pool '" + poolKey + "' in one graph transaction.");
});
}
@Director(description = "Remove the selected piece from an owned pool transactionally", sync = true,
origin = DirectorOrigin.PLAYER)
public void remove(
@Param(description = "Owned pool resource key") String poolKey
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before removing its pool entry.");
return;
}
String pieceKey = selected.pieceKey();
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.removePiece(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
pieceKey);
if (!update.changed()) {
throw new IOException("The owned pool does not reference selected piece '" + pieceKey + "'");
}
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Removed '" + pieceKey + "' from pool '" + poolKey + "' in one graph transaction.");
});
}
@Director(description = "Allow or forbid cardinal rotation of the selected piece", sync = true,
origin = DirectorOrigin.PLAYER)
public void rotatable(
@Param(description = "Whether the selected piece may rotate") boolean rotatable
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before changing rotation.");
return;
}
if (!rotatable
&& context.request().compatibilityTarget()
== JigsawStudioCompatibilityTarget.VANILLA_PORTABLE) {
CommandJigsaw.sendError(this, "Vanilla-portable pieces must remain rotatable.");
return;
}
String pieceKey = selected.pieceKey();
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioGraphEditor.updateRotatable(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
pieceKey,
rotatable);
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Persisted piece '" + pieceKey + "' rotatable=" + rotatable + ".");
});
}
@Director(description = "Resize the selected piece object exactly to workcell capacity", sync = true,
origin = DirectorOrigin.PLAYER)
public void expand() {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay workcell = CommandJigsaw.selectedBay(context);
if (workcell == null) {
CommandJigsaw.sendError(this, "Select a workcell before expanding its active variant.");
return;
}
JigsawStudioService.get().expandVariantToCell(player(), workcell.stableId());
}
}
@@ -0,0 +1,37 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioGraphEditor;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
@Director(name = "pool", description = "Manage owned jigsaw pools", origin = DirectorOrigin.PLAYER)
public class CommandJigsawPool implements DirectorExecutor {
@Director(description = "Create an empty owned jigsaw pool", sync = true,
origin = DirectorOrigin.PLAYER)
public void create(
@Param(description = "New owned pool resource key") String poolKey,
@Param(description = "Owned fallback pool key, or none", defaultValue = "none")
String fallbackPoolKey
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
String fallback = "none".equalsIgnoreCase(fallbackPoolKey) ? "" : fallbackPoolKey;
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioGraphEditor.createPool(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
fallback);
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Created owned pool '" + poolKey + "' with fallback '"
+ (fallback.isBlank() ? "none" : fallback) + "'.");
});
}
}
@@ -0,0 +1,25 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
@Director(name = "preview", description = "Assemble a read-only jigsaw preview",
origin = DirectorOrigin.PLAYER)
public class CommandJigsawPreview implements DirectorExecutor {
@Director(name = "goto", aliases = "teleport", description = "Teleport to the live seed-1337 preview",
sync = true, origin = DirectorOrigin.PLAYER)
public void gotoPreview() {
JigsawStudioService.get().goToPreview(player());
}
@Director(description = "Assemble a deterministic preview at your location", sync = true,
origin = DirectorOrigin.PLAYER)
public void assemble(
@Param(description = "Assembly seed", defaultValue = "1337") long seed
) {
JigsawStudioService.get().previewStudio(player(), seed);
}
}
@@ -0,0 +1,73 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioCompatibilityTarget;
import art.arcane.iris.studio.jigsaw.JigsawStudioPoolEditor;
import art.arcane.iris.studio.jigsaw.JigsawStudioStructureEditor;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
@Director(name = "rules", aliases = "rule", description = "Manage structure-wide jigsaw rules",
origin = DirectorOrigin.PLAYER)
public class CommandJigsawRules implements DirectorExecutor {
@Director(description = "Set maximum expansion depth and horizontal radius", sync = true,
origin = DirectorOrigin.PLAYER)
public void limits(
@Param(description = "Maximum expansion depth") int maxDepth,
@Param(description = "Maximum radius in chunks") int maxSizeChunks
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
if (context.request().compatibilityTarget() == JigsawStudioCompatibilityTarget.VANILLA_PORTABLE
&& (maxDepth > 20 || maxSizeChunks > 8)) {
CommandJigsaw.sendError(this, "Vanilla-portable rules require maxDepth <= 20 and maxSizeChunks <= 8.");
return;
}
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioStructureEditor.updateLimits(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
maxDepth,
maxSizeChunks);
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Persisted jigsaw limits: maxDepth=" + maxDepth
+ ", maxSizeChunks=" + maxSizeChunks + ".");
});
}
@Director(description = "Set or clear one owned pool's direct fallback", sync = true,
origin = DirectorOrigin.PLAYER)
public void fallback(
@Param(description = "Owned pool resource key") String poolKey,
@Param(description = "Owned fallback pool key, or none") String fallbackPoolKey
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
String fallback = "none".equalsIgnoreCase(fallbackPoolKey) ? "" : fallbackPoolKey;
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioPoolEditor.PoolUpdate update = JigsawStudioPoolEditor.updateFallback(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
fallback);
if (!update.changed()) {
return CommandJigsaw.mappedMutationResult(context, "", "", "Pool fallback is unchanged.");
}
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Persisted pool '" + poolKey + "' fallback as '"
+ (fallback.isBlank() ? "none" : fallback) + "'.");
});
}
}
@@ -0,0 +1,168 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.studio.jigsaw.JigsawStudioPoolEditor;
import art.arcane.iris.studio.jigsaw.JigsawStudioVariant;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import java.io.IOException;
@Director(name = "variant", description = "Manage selected jigsaw variants", origin = DirectorOrigin.PLAYER)
public class CommandJigsawVariant implements DirectorExecutor {
@Director(description = "Set the selected piece weight in a pool", sync = true,
origin = DirectorOrigin.PLAYER)
public void weight(
@Param(description = "Pool resource key") String poolKey,
@Param(description = "Positive selection weight") int weight
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioVariant selected = CommandJigsaw.selectedVariant(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before changing its weight.");
return;
}
String pieceKey = selected.pieceKey();
CommandJigsaw.runGraphMutation(player(), context, () -> {
JigsawStudioPoolEditor.WeightUpdate update = JigsawStudioPoolEditor.updateWeight(
context.request().source().getDataFolder().toPath(),
context.request().structureKey(),
poolKey,
pieceKey,
weight);
if (!update.changed()) {
throw new IOException("Pool '" + poolKey + "' does not reference selected piece '"
+ pieceKey + "'");
}
return CommandJigsaw.mappedMutationResult(
context,
"",
"",
"Updated " + update.changedEntries() + " pool entry weight atomically in "
+ update.poolPath() + ".");
});
}
@Director(description = "Resize only the active variant object within workcell capacity", sync = true,
origin = DirectorOrigin.PLAYER)
public void resize(
@Param(description = "Variant width") int width,
@Param(description = "Variant height") int height,
@Param(description = "Variant depth") int depth
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
if (selectedWorkcell == null || selectedVariant == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before resizing it.");
return;
}
JigsawStudioCellDimensions dimensions;
try {
dimensions = new JigsawStudioCellDimensions(width, height, depth);
} catch (IllegalArgumentException exception) {
CommandJigsaw.sendError(this, exception.getMessage());
return;
}
JigsawStudioService.get().resizeVariant(
player(),
selectedWorkcell.stableId(),
selectedVariant.pieceKey(),
dimensions);
}
@Director(description = "Set the active variant's author-facing label", sync = true,
origin = DirectorOrigin.PLAYER)
public void label(
@Param(description = "Display label; quote labels containing spaces") String displayName
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
if (selectedWorkcell == null || selectedVariant == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before renaming it.");
return;
}
JigsawStudioService.get().updateVariantDisplayName(
player(),
selectedWorkcell.stableId(),
selectedVariant.pieceKey(),
displayName);
}
@Director(name = "label-reset", aliases = "reset-label",
description = "Reset the active variant to its resource-key label", sync = true,
origin = DirectorOrigin.PLAYER)
public void labelReset() {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selectedWorkcell = CommandJigsaw.selectedBay(context);
JigsawStudioVariant selectedVariant = CommandJigsaw.selectedVariant(context);
if (selectedWorkcell == null || selectedVariant == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before resetting its label.");
return;
}
JigsawStudioService.get().updateVariantDisplayName(
player(),
selectedWorkcell.stableId(),
selectedVariant.pieceKey(),
"");
}
@Director(description = "Duplicate the active variant in only this workcell", sync = true,
origin = DirectorOrigin.PLAYER)
public void duplicate() {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
if (selected == null || CommandJigsaw.selectedVariant(context) == null) {
CommandJigsaw.sendError(this, "Select a workcell with an active variant before duplicating it.");
return;
}
JigsawStudioService.get().createVariant(player(), selected.stableId(), true);
}
@Director(name = "duplicate-family", aliases = "family",
description = "Duplicate every enabled workcell's active variant as one coherent family",
sync = true, origin = DirectorOrigin.PLAYER)
public void duplicateFamily(
@Param(description = "Family key or next", defaultValue = "next") String themeKey
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
String selectedTheme = themeKey;
if ("next".equalsIgnoreCase(themeKey)) {
try {
selectedTheme = CommandJigsaw.nextThemeKey(context);
} catch (IOException | ArithmeticException exception) {
CommandJigsaw.sendError(this, exception.getMessage());
return;
}
}
JigsawStudioService.get().duplicateActiveFamily(player(), selectedTheme);
}
}
@@ -0,0 +1,80 @@
package art.arcane.iris.command;
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
import art.arcane.iris.studio.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.studio.jigsaw.JigsawStudioService;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
@Director(name = "workcell", aliases = "cell", description = "Manage the selected Studio workcell",
origin = DirectorOrigin.PLAYER)
public class CommandJigsawWorkcell implements DirectorExecutor {
@Director(description = "Set capacity without resizing any variant object", sync = true,
origin = DirectorOrigin.PLAYER)
public void capacity(
@Param(description = "Capacity width") int width,
@Param(description = "Capacity height") int height,
@Param(description = "Capacity depth") int depth
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell before changing its capacity.");
return;
}
JigsawStudioCellDimensions dimensions;
try {
dimensions = new JigsawStudioCellDimensions(width, height, depth);
} catch (IllegalArgumentException exception) {
CommandJigsaw.sendError(this, exception.getMessage());
return;
}
JigsawStudioService.get().updateWorkcellDimensions(
player(),
selected.stableId(),
dimensions);
}
@Director(description = "Set the selected workcell's author-facing label", sync = true,
origin = DirectorOrigin.PLAYER)
public void label(
@Param(description = "Display label; quote labels containing spaces") String displayName
) {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell before renaming it.");
return;
}
JigsawStudioService.get().updateWorkcellDisplayName(
player(),
selected.stableId(),
displayName);
}
@Director(name = "label-reset", aliases = "reset-label",
description = "Reset the selected workcell to its canonical solver label", sync = true,
origin = DirectorOrigin.PLAYER)
public void labelReset() {
CommandJigsaw.ActiveContext context = CommandJigsaw.active(player());
if (context == null) {
CommandJigsaw.sendError(this, "Open a Jigsaw Studio project first.");
return;
}
JigsawStudioBay selected = CommandJigsaw.selectedBay(context);
if (selected == null) {
CommandJigsaw.sendError(this, "Select a workcell before resetting its label.");
return;
}
JigsawStudioService.get().updateWorkcellDisplayName(player(), selected.stableId(), "");
}
}
@@ -0,0 +1,859 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.integration.WorldEditLink;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.PackDirectoryResolver;
import art.arcane.iris.studio.object.ObjectStudioActivation;
import art.arcane.iris.studio.object.StudioOpenCoordinator;
import art.arcane.iris.world.runtime.WorldRuntimeControlService;
import art.arcane.iris.integration.ExternalDataSVC;
import art.arcane.iris.studio.object.ObjectSVC;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.studio.wand.WandSVC;
import art.arcane.iris.pack.IrisConverter;
import art.arcane.iris.studio.tree.TreePlausibilizeBatch;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.object.IObjectPlacer;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.object.IrisObject;
import art.arcane.iris.structure.object.IrisObjectPlacement;
import art.arcane.iris.structure.object.IrisObjectPlacementScaleInterpolator;
import art.arcane.iris.structure.object.IrisObjectRotation;
import art.arcane.iris.structure.object.IrisObjectScale;
import art.arcane.iris.generation.block.TileData;
import art.arcane.iris.platform.bukkit.BukkitBlockState;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.data.Cuboid;
import art.arcane.iris.generation.block.IrisCustomData;
import art.arcane.iris.command.specialhandlers.NullableDimensionHandler;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.command.specialhandlers.ObjectHandler;
import art.arcane.iris.command.specialhandlers.ObjectScaleHandler;
import art.arcane.iris.command.specialhandlers.ObjectTargetHandler;
import art.arcane.iris.localization.C;
import art.arcane.volmlib.util.math.DirectionBasis;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.HeightMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.RuntimeUiMessages;
@Director(name = "object", aliases = "o", origin = DirectorOrigin.PLAYER, description = "Iris object manipulation", descriptionKey = "iris.director.commandobject.director.iris_object_manipulation")
public class CommandObject implements DirectorExecutor {
static final Set<Material> PASTE_TRANSPARENT_BLOCKS = Set.of(
Material.AIR,
Material.CAVE_AIR,
Material.VOID_AIR,
Material.SHORT_GRASS,
Material.SNOW,
Material.VINE,
Material.TORCH,
Material.DEAD_BUSH,
Material.POPPY,
Material.DANDELION
);
static boolean isPasteTarget(Material material) {
return !PASTE_TRANSPARENT_BLOCKS.contains(material);
}
@Director(description = "Open an object studio world (grid of every object; dimension optional, defaults to all packs)", descriptionKey = "iris.director.commandobject.director.open_object_studio_world_grid_every_object_dimension_optional_defaults_all_packs", sync = true)
public void studio(
@Param(defaultValue = "null", description = "Optional dimension whose object pack to lay out; omit to aggregate objects from every pack", descriptionKey = "iris.director.commandobject.param.optional_dimension_whose_object_pack_lay_out_omit_aggregate_objects_from_every", aliases = "dim", customHandler = NullableDimensionHandler.class)
IrisDimension dimension,
@Param(defaultValue = "1337", description = "The seed to generate the studio with", descriptionKey = "iris.director.commandobject.param.seed_generate_studio_with", aliases = "s")
long seed
) {
VolmitSender commandSender = sender();
Map<String, IrisData> sources = new LinkedHashMap<>();
IrisDimension hostDimension = dimension;
if (dimension != null) {
IrisData data = dimension.getLoader();
if (data == null) {
data = IrisData.get(dimension.getLoadFile().getParentFile().getParentFile());
}
sources.put(data.getDataFolder().getName(), data);
} else {
File workspace = Iris.service(StudioSVC.class).getWorkspaceFolder();
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(workspace)) {
File dimensionsDir = new File(pack, "dimensions");
if (!dimensionsDir.isDirectory()) continue;
IrisData data = IrisData.get(pack);
String[] keys = data.getObjectLoader().getPossibleKeys();
if (keys == null || keys.length == 0) continue;
sources.put(pack.getName(), data);
if (hostDimension == null) {
File[] dimFiles = dimensionsDir.listFiles((f) -> f.isFile() && f.getName().endsWith(".json"));
if (dimFiles != null && dimFiles.length > 0) {
String loadKey = dimFiles[0].getName().replaceFirst("\\.json$", "");
IrisDimension loaded = data.getDimensionLoader().load(loadKey);
if (loaded != null) {
hostDimension = loaded;
}
}
}
}
}
if (hostDimension == null || sources.isEmpty()) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_NO_PACKS_WITH_OBJECTS_WERE_FOUND_ON_THIS_SERVER));
return;
}
int totalObjects = 0;
for (IrisData d : sources.values()) {
String[] k = d.getObjectLoader().getPossibleKeys();
if (k != null) totalObjects += k.length;
}
if (totalObjects == 0) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_NO_OBJECTS_PLACE_ACROSS_SELECTED_PACK_S));
return;
}
// ObjectStudioActivation carries the buffet state; mutating the shared cached
// IrisDimension here leaked OBJECT_BUFFET into later pack exports of this dimension.
ObjectStudioActivation.activate(hostDimension.getLoadKey());
ObjectStudioActivation.setSources(hostDimension.getLoadKey(), sources);
String scope = dimension == null
? ("all packs [" + sources.size() + "]")
: ("\"" + hostDimension.getName() + "\"");
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_OPENING_OBJECT_STUDIO_OBJECTS, MessageArgument.untrusted("scope", scope), MessageArgument.untrusted("totalObjects", totalObjects)));
IrisDimension finalHost = hostDimension;
try {
Iris.service(StudioSVC.class).open(
commandSender,
seed,
hostDimension.getLoadKey(),
StudioOpenCoordinator.StudioOpenKind.OBJECT,
world -> {
if (world == null) return;
try {
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
} catch (Throwable e) {
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e);
}
if (commandSender.isPlayer()) {
Player p = commandSender.player();
if (p != null) {
Location target = new Location(world, 0.5D, 66D, 0.5D);
J.runEntity(p, () -> {
BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE));
});
}
}
});
} catch (Throwable e) {
Iris.reportError("Failed to open object studio world \"" + finalHost.getLoadKey() + "\".", e);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_FAILED_OPEN_OBJECT_STUDIO, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
}
}
public static IObjectPlacer createPlacer(World world, Map<Block, BlockData> futureBlockChanges, Engine targetEngine) {
return new IObjectPlacer() {
@Override
public int getHighest(int x, int z, IrisData data) {
return world.getHighestBlockYAt(x, z);
}
@Override
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
return world.getHighestBlockYAt(x, z, ignoreFluid ? HeightMap.OCEAN_FLOOR : HeightMap.MOTION_BLOCKING);
}
@Override
public void set(int x, int y, int z, PlatformBlockState s) {
BlockData d = (BlockData) s.nativeHandle();
Block block = world.getBlockAt(x, y, z);
//Prevent blocks being set in or bellow bedrock
if (y <= world.getMinHeight() || block.getType() == Material.BEDROCK) return;
ExternalDataSVC external = IrisServices.getOrNull(ExternalDataSVC.class);
futureBlockChanges.computeIfAbsent(block, target -> {
BlockData previous = target.getBlockData();
return external == null ? previous : external.captureBlockData(previous);
});
if (d instanceof IrisCustomData data) {
if (external != null && external.placeBlock(block, data.getCustom())) {
return;
}
block.setBlockData(data.getBase(), false);
IrisLogging.warnOnce("object-paste:external:" + data.getCustom(),
"Direct placement of %s is unavailable; only its base block was placed.", data.getCustom());
} else block.setBlockData(d, false);
}
@Override
public PlatformBlockState get(int x, int y, int z) {
return BukkitBlockState.of(world.getBlockAt(x, y, z).getBlockData());
}
@Override
public boolean isPreventingDecay() {
return false;
}
@Override
public boolean isCarved(int x, int y, int z) {
return false;
}
@Override
public boolean isSolid(int x, int y, int z) {
return world.getBlockAt(x, y, z).getType().isSolid();
}
@Override
public boolean isUnderwater(int x, int z) {
return false;
}
@Override
public int getFluidHeight() {
return targetEngine == null
? 63
: targetEngine.getMinHeight() + targetEngine.getDimension().getFluidHeight();
}
@Override
public boolean isDebugSmartBore() {
return false;
}
@Override
public void setTile(int xx, int yy, int zz, TileData tile) {
tile.toBukkitTry(world.getBlockAt(xx, yy, zz));
}
@Override
public <T> void setData(int xx, int yy, int zz, T data) {
}
@Override
public <T> T getData(int xx, int yy, int zz, Class<T> t) {
return null;
}
@Override
public Engine getEngine() {
return targetEngine;
}
};
}
@Director(description = "Check the composition of an object", descriptionKey = "iris.director.commandobject.director.check_composition_object")
public void analyze(
@Param(description = "The object to analyze", descriptionKey = "iris.director.commandobject.param.object_analyze", customHandler = ObjectHandler.class)
String object
) {
IrisObject o = IrisData.loadAnyObject(object, data());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_OBJECT_SIZE, MessageArgument.untrusted("value", o.getW()), MessageArgument.untrusted("value2", o.getH()), MessageArgument.untrusted("value3", o.getD())));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_BLOCKS_USED, MessageArgument.untrusted("value", NumberFormat.getIntegerInstance().format(o.getBlocks().size()))));
var queue = o.getBlocks().values();
Map<Material, Set<BlockData>> unsorted = new HashMap<>();
Map<BlockData, Integer> amounts = new HashMap<>();
Map<Material, Integer> materials = new HashMap<>();
while (queue.hasNext()) {
BlockData block = (BlockData) queue.next().nativeHandle();
//unsorted.put(block.getMaterial(), block);
if (!amounts.containsKey(block)) {
amounts.put(block, 1);
} else
amounts.put(block, amounts.get(block) + 1);
if (!materials.containsKey(block.getMaterial())) {
materials.put(block.getMaterial(), 1);
unsorted.put(block.getMaterial(), new HashSet<>());
unsorted.get(block.getMaterial()).add(block);
} else {
materials.put(block.getMaterial(), materials.get(block.getMaterial()) + 1);
unsorted.get(block.getMaterial()).add(block);
}
}
List<Material> sortedMatsList = amounts.keySet().stream().map(BlockData::getMaterial)
.sorted().toList();
Set<Material> sortedMats = new TreeSet<>(Comparator.comparingInt(materials::get).reversed());
sortedMats.addAll(sortedMatsList);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_BLOCKS_OBJECT));
int n = 0;
for (Material mat : sortedMats) {
int amount = materials.get(mat);
List<BlockData> set = new ArrayList<>(unsorted.get(mat));
set.sort(Comparator.comparingInt(amounts::get).reversed());
BlockData data = set.get(0);
int dataAmount = amounts.get(data);
String string = " - " + mat.toString() + "*" + amount;
if (data.getAsString(true).contains("[")) {
string = string + " --> [" + data.getAsString(true).split("\\[")[1]
.replaceAll("true", ChatColor.GREEN + "true" + ChatColor.GRAY)
.replaceAll("false", ChatColor.RED + "false" + ChatColor.GRAY) + "*" + dataAmount;
}
sender().sendMessage(string);
n++;
if (n >= 10) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_OTHER_BLOCK_TYPES, MessageArgument.untrusted("value", (sortedMats.size() - n))));
return;
}
}
}
@Director(description = "Shrink an object to its minimum size", descriptionKey = "iris.director.commandobject.director.shrink_object_its_minimum_size")
public void shrink(@Param(description = "The object to shrink", descriptionKey = "iris.director.commandobject.param.object_shrink", customHandler = ObjectHandler.class) String object) {
IrisObject o = IrisData.loadAnyObject(object, data());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_CURRENT_OBJECT_SIZE, MessageArgument.untrusted("value", o.getW()), MessageArgument.untrusted("value2", o.getH()), MessageArgument.untrusted("value3", o.getD())));
o.shrinkwrap();
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NEW_OBJECT_SIZE, MessageArgument.untrusted("value", o.getW()), MessageArgument.untrusted("value2", o.getH()), MessageArgument.untrusted("value3", o.getD())));
try {
o.write(o.getLoadFile());
} catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", o.getLoadFile()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
Iris.reportError("Failed to save object " + o.getLoadFile() + ".", e);
}
}
@Director(description = "Grow organic branches through the canopy so every leaf survives vanilla decay", descriptionKey = "iris.director.commandobject.director.grow_organic_branches_through_canopy_so_every_leaf_survives_vanilla_decay",
origin = DirectorOrigin.BOTH)
public void plausibilize(
@Param(description = "Object key, prefix (trees/), or filesystem path", descriptionKey = "iris.director.commandobject.param.object_key_prefix_trees_filesystem_path",
customHandler = ObjectTargetHandler.class)
String target,
@Param(name = "dryrun", description = "dryrun=true analyzes only, writes nothing", descriptionKey = "iris.director.commandobject.param.dryrun_true_analyzes_only_writes_nothing", defaultValue = "false")
boolean dryRun,
@Param(name = "reach", description = "reach=N max branch length in blocks from existing wood; farther leaf clusters are pinned persistent instead. reach=0 grows unlimited", descriptionKey = "iris.director.commandobject.param.reach_n_max_branch_length_blocks_from_existing_wood_farther_leaf_clusters", defaultValue = "12")
int reach
) {
IrisData nearest = data();
List<TreePlausibilizeBatch.Target> targets = TreePlausibilizeBatch.resolve(target, nearest);
if (targets.isEmpty() && nearest == null) {
targets = resolveFromPacks(target);
}
if (targets.isEmpty()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NO_OBJECTS_MATCHED, MessageArgument.untrusted("target", target)));
return;
}
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_OBJECT_PLAUSIBILIZE_REACH_QUEUED_OBJECT_S,
MessageArgument.trusted("reach", reach),
MessageArgument.trusted("value", dryRun ? IrisLanguage.text(RuntimeUiMessages.TREE_DRY_SUFFIX) : ""),
MessageArgument.trusted("value2", targets.size())
));
VolmitSender s = sender();
List<TreePlausibilizeBatch.Target> queued = targets;
J.a(() -> TreePlausibilizeBatch.run(queued, dryRun, reach, nearest, (TreePlausibilizeBatch.Output output) ->
s.sendMessage(output.headline()
? C.IRIS + output.text()
: C.GRAY + " " + output.text())));
}
private static List<TreePlausibilizeBatch.Target> resolveFromPacks(String target) {
List<TreePlausibilizeBatch.Target> out = new ArrayList<>();
File packsFolder = Iris.instance.getDataFolder("packs");
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsFolder)) {
File objectsRoot = new File(pack, "objects");
if (!objectsRoot.isDirectory()) {
continue;
}
File candidate = new File(objectsRoot, target + ".iob");
if (candidate.isFile()) {
out.add(new TreePlausibilizeBatch.Target(pack.getName() + "/" + target, candidate));
continue;
}
File candidateDir = new File(objectsRoot, target);
if (candidateDir.isDirectory()) {
TreePlausibilizeBatch.walkIob(candidateDir, objectsRoot, out);
}
}
return out;
}
@Director(description = "Convert .schem files in the 'convert' folder to .iob files.", descriptionKey = "iris.director.commandobject.director.convert_schem_files_convert_folder_iob_files")
public void convert () {
try {
IrisConverter.convertSchematics(sender());
} catch (Exception e) {
Iris.reportError("Failed to convert schematics to Iris objects.", e);
}
}
@Director(description = "Get a powder that reveals objects", descriptionKey = "iris.director.commandobject.director.get_powder_that_reveals_objects", aliases = "d")
public void dust() {
VolmitSender commandSender = sender();
Player player = player();
onPlayerThread(player, () -> {
player.getInventory().addItem(WandSVC.createDust());
commandSender.playSound(Sound.AMBIENT_SOUL_SAND_VALLEY_ADDITIONS, 1f, 1.5f);
});
}
@Director(description = "Contract a selection based on your looking direction", descriptionKey = "iris.director.commandobject.director.contract_selection_based_on_your_looking_direction", aliases = "-")
public void contract(
@Param(description = "The amount to inset by", descriptionKey = "iris.director.commandobject.param.amount_inset_by", defaultValue = "1")
int amount
) {
VolmitSender commandSender = sender();
Player player = player();
onPlayerThread(player, () -> {
if (!WandSVC.isHoldingWand(player)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_HOLD_YOUR_WAND));
return;
}
Location[] b = WandSVC.getCuboid(player);
if (b == null || b[0] == null || b[1] == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NO_AREA_SELECTED));
return;
}
Location a1 = b[0].clone();
Location a2 = b[1].clone();
Cuboid cursor = new Cuboid(a1, a2);
DirectionBasis d = DirectionBasis.closest(player.getLocation().getDirection()).reverse();
assert d != null;
cursor = cursor.expand(d.getCuboidDirection(), -amount);
b[0] = cursor.getLowerNE();
b[1] = cursor.getUpperSW();
player.getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
player.updateInventory();
commandSender.playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
});
}
@Director(description = "Set point 1 to look", descriptionKey = "iris.director.commandobject.director.set_point_1_look", aliases = "p1")
public void position1(
@Param(description = "Whether to use your current position, or where you look", descriptionKey = "iris.director.commandobject.param.whether_use_your_current_position_where_you_look", defaultValue = "true")
boolean here
) {
VolmitSender commandSender = sender();
Player player = player();
onPlayerThread(player, () -> {
if (!WandSVC.isHoldingWand(player)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_READY_YOUR_WAND));
return;
}
if (WandSVC.isHoldingWand(player)) {
Location[] g = WandSVC.getCuboid(player);
if (g == null) {
return;
}
if (!here) {
// TODO: WARNING HEIGHT
g[1] = player.getTargetBlock(null, 256).getLocation().clone();
} else {
g[1] = player.getLocation().getBlock().getLocation().clone().add(0, -1, 0);
}
player.getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
}
});
}
@Director(description = "Set point 2 to look", descriptionKey = "iris.director.commandobject.director.set_point_2_look", aliases = "p2")
public void position2(
@Param(description = "Whether to use your current position, or where you look", descriptionKey = "iris.director.commandobject.param.whether_use_your_current_position_where_you_look_2", defaultValue = "true")
boolean here
) {
VolmitSender commandSender = sender();
Player player = player();
onPlayerThread(player, () -> {
if (!WandSVC.isHoldingWand(player)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_READY_YOUR_WAND_2));
return;
}
Location[] g = WandSVC.getCuboid(player);
if (g == null) {
return;
}
if (!here) {
// TODO: WARNING HEIGHT
g[0] = player.getTargetBlock(null, 256).getLocation().clone();
} else {
g[0] = player.getLocation().getBlock().getLocation().clone().add(0, -1, 0);
}
player.getInventory().setItemInMainHand(WandSVC.createWand(g[0], g[1]));
});
}
@Director(description = "Paste an object", descriptionKey = "iris.director.commandobject.director.paste_object", sync = true)
public void paste(
@Param(description = "The object to paste", descriptionKey = "iris.director.commandobject.param.object_paste", customHandler = ObjectHandler.class)
String object,
@Param(description = "Whether or not to edit the object (need to hold wand)", descriptionKey = "iris.director.commandobject.param.whether_not_edit_object_need_hold_wand", defaultValue = "false")
boolean edit,
@Param(description = "The amount of degrees to rotate by", descriptionKey = "iris.director.commandobject.param.amount_degrees_rotate_by", defaultValue = "0")
int rotate,
@Param(description = "Explicit scale factor, or dimension to inherit the current Iris world's factor", descriptionKey = "iris.director.commandobject.param.factor_by_which_scale_object_placement", defaultValue = "dimension", customHandler = ObjectScaleHandler.class)
Double scale
// ,
// @Param(description = "The scale interpolator to use", descriptionKey = "iris.director.commandobject.param.scale_interpolator_use", defaultValue = "none")
// IrisObjectPlacementScaleInterpolator interpolator
) {
IrisObject o = IrisData.loadAnyObject(object, data());
Engine placementEngine = engine();
double factor = scale == null
? placementEngine == null ? 1D : placementEngine.getDimension().getAllObjectScaleFactor()
: IrisObjectScale.requireValidFactor(scale, "scale");
double maxScale = Double.max(10 - o.getBlocks().size() / 10000d, 1);
if (factor > maxScale) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_INDICATED_SCALE_EXCEEDS_MAXIMUM_DOWNSCALED_MAXIMUM, MessageArgument.untrusted("maxScale", maxScale)));
factor = maxScale;
}
IrisObjectPlacement placement = new IrisObjectPlacement();
placement.setRotation(IrisObjectRotation.of(0, rotate, 0));
VolmitSender commandSender = sender();
Player player = player();
ItemStack wand = player.getInventory().getItemInMainHand();
Block targetBlock = player.getTargetBlock(PASTE_TRANSPARENT_BLOCKS, 256);
if (!isPasteTarget(targetBlock.getType())) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_WHAT_PLEASE_LOOK_AT_ANY_BLOCK_NOT_AT_SKY));
return;
}
Location block = targetBlock.getLocation().clone().add(0, 1, 0);
commandSender.playSound(Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.5f);
Map<Block, BlockData> futureChanges = new HashMap<>();
if (factor != 1D) {
o = scale == null ? IrisObjectScale.getFixed(o, factor)
: new IrisObjectScale().setSize(factor)
.setInterpolation(IrisObjectPlacementScaleInterpolator.TRICUBIC).get(new RNG(), o);
}
// Block writes must run on the thread owning the target chunk; the undo log stays global.
final IrisObject placed = o;
if (!J.runAt(block, () -> {
placed.place(block.getBlockX(), block.getBlockY() + (int) placed.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges, null), placement, new RNG(), placed.getLoader());
J.runGlobal(() -> Iris.service(ObjectSVC.class).addChanges(futureChanges));
if (!edit) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_PLACED, MessageArgument.untrusted("object", object)));
return;
}
onPlayerThread(player, () -> {
ObjectPasteBounds bounds = ObjectPasteBounds.resolve(placed, placement.getRotation(), block.getBlockX(),
block.getBlockY() + placed.getCenter().getBlockY(), block.getBlockZ());
Location minimum = new Location(block.getWorld(), bounds.minX(), bounds.minY(), bounds.minZ());
Location maximum = new Location(block.getWorld(), bounds.maxX(), bounds.maxY(), bounds.maxZ());
ItemStack newWand = WandSVC.createWand(maximum, minimum);
if (WandSVC.isWand(wand)) {
player.getInventory().setItemInMainHand(newWand);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_UPDATED_WAND_OBJECTS_IOB, MessageArgument.untrusted("value", placed.getLoadKey())));
} else {
int slot = WandSVC.findWand(player.getInventory());
if (slot == -1) {
player.getInventory().addItem(newWand);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_GIVEN_NEW_WAND_OBJECTS_IOB, MessageArgument.untrusted("value", placed.getLoadKey())));
} else {
player.getInventory().setItem(slot, newWand);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_UPDATED_WAND_OBJECTS_IOB_2, MessageArgument.untrusted("value", placed.getLoadKey())));
}
}
});
})) {
Iris.warn("Could not schedule the object paste at " + block.getBlockX() + ", " + block.getBlockY() + ", " + block.getBlockZ() + ".");
}
}
/**
* Runs the body on the thread owning the player, reporting when the hop cannot be scheduled.
*/
private void onPlayerThread(Player player, Runnable body) {
if (player == null) {
return;
}
if (!J.runEntity(player, body)) {
Iris.warn("Could not schedule /iris object on the thread owning " + player.getName() + ".");
}
}
@Director(description = "Save an object", descriptionKey = "iris.director.commandobject.director.save_object")
public void save(
@Param(description = "The dimension to store the object in", descriptionKey = "iris.director.commandobject.param.dimension_store_object", contextual = true, contextualOverride = true)
IrisDimension dimension,
@Param(description = "The file to store it in, can use / for subfolders", descriptionKey = "iris.director.commandobject.param.file_store_it_can_use_subfolders")
String name,
@Param(description = "Overwrite existing object files", descriptionKey = "iris.director.commandobject.param.overwrite_existing_object_files", defaultValue = "false", aliases = "force")
boolean overwrite,
@Param(description = "Use legacy TileState serialization if possible", descriptionKey = "iris.director.commandobject.param.use_legacy_tilestate_serialization_if_possible", defaultValue = "true")
boolean legacy
) {
IrisObject o = WandSVC.createSchematic(player(), legacy);
if (o == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_YOU_NEED_HOLD_YOUR_WAND));
return;
}
File file = Iris.service(StudioSVC.class).getWorkspaceFile(dimension.getLoadKey(), "objects", name + ".iob");
if (file.exists() && !overwrite) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FILE_ALREADY_EXISTS_SET_OVERWRITE_TRUE_OVERWRITE_IT));
return;
}
try {
o.write(file, sender());
} catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FAILED_SAVE_OBJECT_BECAUSE_IOEXCEPTION, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
Iris.reportError(e);
return;
}
sender().playSound(Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.5f);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_SUCCESSFULLY_OBJECT_SAVED_OBJECTS, MessageArgument.untrusted("value", dimension.getLoadKey()), MessageArgument.untrusted("name", name)));
}
@Director(description = "Shift a selection in your looking direction", descriptionKey = "iris.director.commandobject.director.shift_selection_your_looking_direction")
public void shift(
@Param(description = "The amount to shift by", descriptionKey = "iris.director.commandobject.param.amount_shift_by", defaultValue = "1")
int amount
) {
VolmitSender commandSender = sender();
Player player = player();
onPlayerThread(player, () -> {
if (!WandSVC.isHoldingWand(player)) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_HOLD_YOUR_WAND_2));
return;
}
Location[] b = WandSVC.getCuboid(player);
if (b == null || b[0] == null || b[1] == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NO_AREA_SELECTED_2));
return;
}
Location a1 = b[0].clone();
Location a2 = b[1].clone();
DirectionBasis d = DirectionBasis.closest(player.getLocation().getDirection()).reverse();
if (d == null) {
return; // HOW DID THIS HAPPEN
}
a1.add(d.toVector().multiply(amount));
a2.add(d.toVector().multiply(amount));
Cuboid cursor = new Cuboid(a1, a2);
b[0] = cursor.getLowerNE();
b[1] = cursor.getUpperSW();
player.getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
player.updateInventory();
commandSender.playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
});
}
@Director(description = "Undo a number of pastes", descriptionKey = "iris.director.commandobject.director.undo_number_pastes", aliases = "u")
public void undo(
@Param(description = "The amount of pastes to undo", descriptionKey = "iris.director.commandobject.param.amount_pastes_undo", defaultValue = "1")
int amount
) {
ObjectSVC service = Iris.service(ObjectSVC.class);
int actualReverts = Math.min(service.getUndos().size(), amount);
service.revertChanges(actualReverts);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_REVERTED_PASTES, MessageArgument.untrusted("actualReverts", actualReverts)));
}
@Director(description = "Gets an object wand and grabs the current WorldEdit selection.", descriptionKey = "iris.director.commandobject.director.gets_object_wand_grabs_current_worldedit_selection", aliases = "we", origin = DirectorOrigin.PLAYER)
public void we() {
VolmitSender commandSender = sender();
Player player = player();
if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_YOU_CAN_T_GET_WORLDEDIT_SELECTION_WITHOUT_WORLDEDIT_YOU_KNOW));
return;
}
Cuboid locs = WorldEditLink.getSelection(player);
if (locs == null) {
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_YOU_DON_T_HAVE_WORLDEDIT_SELECTION_THIS_WORLD));
return;
}
onPlayerThread(player, () -> {
player.getInventory().addItem(WandSVC.createWand(locs.getLowerNE(), locs.getUpperSW()));
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FRESH_WAND_WITH_YOUR_CURRENT_WORLDEDIT_SELECTION_ON_IT));
});
}
@Director(description = "Get an object wand", descriptionKey = "iris.director.commandobject.director.get_object_wand", sync = true)
public void wand() {
player().getInventory().addItem(WandSVC.createWand());
sender().playSound(Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f, 1.5f);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_POOF_GOOD_LUCK_BUILDING));
}
@Director(name = "x&y", description = "Autoselect up, down & out", descriptionKey = "iris.director.commandobject.director.autoselect_up_down_out", sync = true)
public void xay() {
if (!WandSVC.isHoldingWand(player())) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_HOLD_YOUR_WAND_3));
return;
}
Location[] b = WandSVC.getCuboid(player());
if (b == null || b[0] == null || b[1] == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NO_AREA_SELECTED_3));
return;
}
Location a1 = b[0].clone();
Location a2 = b[1].clone();
Location a1x = b[0].clone();
Location a2x = b[1].clone();
Cuboid cursor = new Cuboid(a1, a2);
Cuboid cursorx = new Cuboid(a1, a2);
while (!cursor.containsOnly(Material.AIR)) {
a1.add(new org.bukkit.util.Vector(0, 1, 0));
a2.add(new org.bukkit.util.Vector(0, 1, 0));
cursor = new Cuboid(a1, a2);
}
a1.add(new org.bukkit.util.Vector(0, -1, 0));
a2.add(new org.bukkit.util.Vector(0, -1, 0));
while (!cursorx.containsOnly(Material.AIR)) {
a1x.add(new org.bukkit.util.Vector(0, -1, 0));
a2x.add(new org.bukkit.util.Vector(0, -1, 0));
cursorx = new Cuboid(a1x, a2x);
}
a1x.add(new org.bukkit.util.Vector(0, 1, 0));
a2x.add(new Vector(0, 1, 0));
b[0] = a1;
b[1] = a2x;
cursor = new Cuboid(b[0], b[1]);
cursor = cursor.contract(Cuboid.CuboidDirection.North);
cursor = cursor.contract(Cuboid.CuboidDirection.South);
cursor = cursor.contract(Cuboid.CuboidDirection.East);
cursor = cursor.contract(Cuboid.CuboidDirection.West);
b[0] = cursor.getLowerNE();
b[1] = cursor.getUpperSW();
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
player().updateInventory();
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_AUTO_SELECT_COMPLETE));
}
@Director(name = "x+y", description = "Autoselect up & out", descriptionKey = "iris.director.commandobject.director.autoselect_up_out", sync = true)
public void xpy() {
if (!WandSVC.isHoldingWand(player())) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_HOLD_YOUR_WAND_4));
return;
}
Location[] b = WandSVC.getCuboid(player());
if (b == null || b[0] == null || b[1] == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_NO_AREA_SELECTED_4));
return;
}
b[0].add(new Vector(0, 1, 0));
b[1].add(new Vector(0, 1, 0));
Location a1 = b[0].clone();
Location a2 = b[1].clone();
Cuboid cursor = new Cuboid(a1, a2);
while (!cursor.containsOnly(Material.AIR)) {
a1.add(new Vector(0, 1, 0));
a2.add(new Vector(0, 1, 0));
cursor = new Cuboid(a1, a2);
}
a1.add(new Vector(0, -1, 0));
a2.add(new Vector(0, -1, 0));
b[0] = a1;
a2 = b[1];
cursor = new Cuboid(a1, a2);
cursor = cursor.contract(Cuboid.CuboidDirection.North);
cursor = cursor.contract(Cuboid.CuboidDirection.South);
cursor = cursor.contract(Cuboid.CuboidDirection.East);
cursor = cursor.contract(Cuboid.CuboidDirection.West);
b[0] = cursor.getLowerNE();
b[1] = cursor.getUpperSW();
player().getInventory().setItemInMainHand(WandSVC.createWand(b[0], b[1]));
player().updateInventory();
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_AUTO_SELECT_COMPLETE_2));
}
}
@@ -0,0 +1,420 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.validation.PackCompatReport;
import art.arcane.iris.pack.PackDirectoryResolver;
import art.arcane.iris.pack.PackResourceCleanup;
import art.arcane.iris.pack.PackValidationCache;
import art.arcane.iris.pack.PackValidationRegistry;
import art.arcane.iris.pack.PackValidationResult;
import art.arcane.iris.pack.PackValidator;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.command.handlers.DimensionHandler;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.localization.TextKey;
import art.arcane.iris.spi.IrisPlatforms;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitRuntimeMessages;
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance", descriptionKey = "iris.director.commandpack.director.pack_validation_maintenance")
public class CommandPack implements DirectorExecutor {
/**
* Director treats a blank defaultValue as "required", which made the all-packs branch
* unreachable from chat. The "*" sentinel keeps the parameter genuinely optional; a blank
* value (the documented "pack=" escape hatch) still means every pack.
*/
static boolean wantsAllPacks(String pack) {
return pack == null || pack.isBlank() || "*".equals(pack.trim());
}
@Director(description = "Validate a pack (or all packs) and re-publish results", descriptionKey = "iris.director.commandpack.director.validate_pack_all_packs_re_publish_results", aliases = {"v"})
public void validate(
@Param(description = "The pack folder name to validate, or * for every pack", descriptionKey = "iris.director.commandpack.param.pack_folder_name_validate_leave_empty_all", defaultValue = "*")
String pack
) {
VolmitSender s = sender();
File packsRoot = Iris.instance.getDataFolder("packs");
if (!packsRoot.isDirectory()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_PACKS_FOLDER_NOT_FOUND));
return;
}
if (wantsAllPacks(pack)) {
List<File> dirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
if (dirs.isEmpty()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_PACKS_VALIDATE));
return;
}
int broken = 0;
for (File dir : dirs) {
PackValidationResult result = runValidate(s, dir);
if (result != null && !result.isLoadable()) {
broken++;
}
}
persistValidationCache(packsRoot);
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_VALIDATION_COMPLETE_BROKEN_PACKS, MessageArgument.untrusted("broken", String.valueOf(broken)), MessageArgument.untrusted("value", String.valueOf(dirs.size()))));
return;
}
File target = PackDirectoryResolver.resolveExisting(packsRoot, pack);
if (target == null) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_PACK_NOT_FOUND_UNDER_PACKS, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
runValidate(s, target);
persistValidationCache(packsRoot);
}
@Director(description = "Package a validated dimension into a compressed format", descriptionKey = "iris.director.commandstudio.director.package_dimension_into_compressed_format", aliases = "package")
public void pkg(
@Param(name = "dimension", description = "The dimension pack to compress", descriptionKey = "iris.director.commandstudio.param.dimension_pack_compress", contextual = true, contextualOverride = true, defaultValue = "default", customHandler = DimensionHandler.class)
IrisDimension dimension,
@Param(name = "obfuscate", description = "Whether or not to obfuscate the pack", descriptionKey = "iris.director.commandstudio.param.whether_not_obfuscate_pack", defaultValue = "false")
boolean obfuscate,
@Param(name = "minify", description = "Whether or not to minify the pack", descriptionKey = "iris.director.commandstudio.param.whether_not_minify_pack", defaultValue = "true")
boolean minify
) {
Iris.service(StudioSVC.class).compilePackage(sender(), dimension.getLoadKey(), obfuscate, minify);
}
@Director(description = "Preview or apply unused-resource cleanup", descriptionKey = "iris.director.commandpack.director.preview_apply_unused_resource_cleanup", aliases = {"c"})
public void cleanup(
@Param(description = "The pack folder name to clean", descriptionKey = "iris.director.commandpack.param.pack_folder_name_clean")
String pack,
@Param(description = "preview or apply", descriptionKey = "iris.director.commandpack.param.preview_apply", defaultValue = "preview")
String mode
) {
VolmitSender s = sender();
File packFolder = findPack(s, pack);
if (packFolder == null) {
return;
}
if ("apply".equalsIgnoreCase(mode)) {
PackResourceCleanup.ApplyResult result = PackResourceCleanup.apply(packFolder);
if (!result.success()) {
s.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_CLEANUP_FAILED,
MessageArgument.untrusted("error", String.valueOf(result.error()))
));
reportPaths(s, result.quarantinedPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_STILL_QUARANTINED);
return;
}
if (!result.changed()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_CLEANUP_CANDIDATES_FOUND_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
PackValidationRegistry.remove(packFolder.getName());
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_QUARANTINED_CLEANUP_CANDIDATE_S_UNDER, MessageArgument.untrusted("size", String.valueOf(result.quarantinedPaths().size())), MessageArgument.untrusted("quarantinePath", String.valueOf(result.quarantinePath()))));
reportPaths(s, result.quarantinedPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_QUARANTINED);
return;
}
if (!"preview".equalsIgnoreCase(mode)) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_CLEANUP_MODE_MUST_BE_PREVIEW_APPLY));
return;
}
PackResourceCleanup.Preview preview = PackResourceCleanup.preview(packFolder);
if (!preview.success()) {
s.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_CLEANUP_FAILED,
MessageArgument.untrusted("error", String.valueOf(preview.error()))
));
return;
}
if (!preview.hasCandidates()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_CLEANUP_CANDIDATES_FOUND_PACK_2, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_CLEANUP_PREVIEW_PACK_CANDIDATE_S_NO_FILES_WERE_CHANGED, MessageArgument.untrusted("pack", String.valueOf(pack)), MessageArgument.untrusted("size", String.valueOf(preview.candidatePaths().size()))));
reportPaths(s, preview.candidatePaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_CANDIDATE);
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RUN_IRIS_PACK_CLEANUP_MODE_APPLY_QUARANTINE_THESE_CANDIDATES_AFTER_FRESH_SCAN, MessageArgument.untrusted("pack", String.valueOf(pack))));
}
@Director(description = "Preview or apply restoration of the latest quarantine", descriptionKey = "iris.director.commandpack.director.preview_apply_restoration_latest_quarantine", aliases = {"r"})
public void restore(
@Param(description = "The pack folder name to restore", descriptionKey = "iris.director.commandpack.param.pack_folder_name_restore")
String pack,
@Param(description = "preview or apply", descriptionKey = "iris.director.commandpack.param.preview_apply_2", defaultValue = "preview")
String mode
) {
VolmitSender s = sender();
File packFolder = findPack(s, pack);
if (packFolder == null) {
return;
}
if ("apply".equalsIgnoreCase(mode)) {
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(packFolder);
if (!result.conflicts().isEmpty()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORE_REFUSED_BECAUSE_DESTINATION_S_ALREADY_EXIST, MessageArgument.untrusted("size", String.valueOf(result.conflicts().size()))));
reportPaths(s, result.conflicts(), BukkitRuntimeMessages.COMMAND_PACK_PATH_CONFLICT);
return;
}
if (!result.success()) {
s.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_RESTORE_FAILED,
MessageArgument.untrusted("error", String.valueOf(result.error()))
));
return;
}
if (!result.changed()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NOTHING_RESTORE_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
PackValidationRegistry.remove(packFolder.getName());
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORED_FILE_S_FROM, MessageArgument.untrusted("size", String.valueOf(result.restoredPaths().size())), MessageArgument.untrusted("dumpPath", String.valueOf(result.dumpPath()))));
reportPaths(s, result.restoredPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_RESTORED);
return;
}
if (!"preview".equalsIgnoreCase(mode)) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORE_MODE_MUST_BE_PREVIEW_APPLY));
return;
}
PackResourceCleanup.RestorePreview preview = PackResourceCleanup.previewRestore(packFolder);
if (!preview.success()) {
s.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_RESTORE_FAILED,
MessageArgument.untrusted("error", String.valueOf(preview.error()))
));
return;
}
if (!preview.hasFiles()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NOTHING_RESTORE_PACK_2, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORE_PREVIEW_FILE_S_NO_FILES_WERE_CHANGED, MessageArgument.untrusted("dumpPath", String.valueOf(preview.dumpPath())), MessageArgument.untrusted("size", String.valueOf(preview.filePaths().size()))));
reportPaths(s, preview.filePaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_FILE);
if (!preview.conflicts().isEmpty()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORE_IS_BLOCKED_BY_EXISTING_DESTINATION_S, MessageArgument.untrusted("size", String.valueOf(preview.conflicts().size()))));
reportPaths(s, preview.conflicts(), BukkitRuntimeMessages.COMMAND_PACK_PATH_CONFLICT);
return;
}
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RUN_IRIS_PACK_RESTORE_MODE_APPLY_RESTORE_AFTER_FRESH_CONFLICT_CHECK, MessageArgument.untrusted("pack", String.valueOf(pack))));
}
@Director(description = "Show cached validation status for a pack", descriptionKey = "iris.director.commandpack.director.show_cached_validation_status_pack", aliases = {"s"})
public void status(
@Param(description = "The pack folder name, or * for every pack", descriptionKey = "iris.director.commandpack.param.pack_folder_name", defaultValue = "*")
String pack
) {
VolmitSender s = sender();
if (wantsAllPacks(pack)) {
if (PackValidationRegistry.snapshot().isEmpty()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_VALIDATION_RESULTS_RECORDED_RUN_IRIS_PACK_VALIDATE_FIRST));
return;
}
PackValidationRegistry.snapshot().forEach((name, result) -> {
TextKey key = result.isLoadable()
? BukkitRuntimeMessages.COMMAND_PACK_STATUS_OK
: BukkitRuntimeMessages.COMMAND_PACK_STATUS_BROKEN;
s.sendMessage(IrisLanguage.text(
key,
MessageArgument.untrusted("pack", name),
MessageArgument.trusted("blocking", result.getBlockingErrors().size()),
MessageArgument.trusted("warnings", result.getWarnings().size())
));
});
return;
}
PackValidationResult result = PackValidationRegistry.get(pack);
if (result == null) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_VALIDATION_RESULT_RUN_IRIS_PACK_VALIDATE, MessageArgument.untrusted("pack", String.valueOf(pack)), MessageArgument.untrusted("pack2", String.valueOf(pack))));
return;
}
reportResult(s, result);
}
@Director(description = "Show pack content that cannot generate on this Minecraft version", descriptionKey = "iris.director.commandpack.director.show_content_cannot_generate_this_minecraft_version", aliases = {"cp"})
public void compat(
@Param(description = "The pack folder name to report on, or * for every pack", descriptionKey = "iris.director.commandpack.param.pack_folder_name_compat", defaultValue = "*")
String pack
) {
VolmitSender s = sender();
if (wantsAllPacks(pack)) {
Map<String, PackValidationResult> snapshot = PackValidationRegistry.snapshot();
if (snapshot.isEmpty()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_VALIDATION_RESULTS_RECORDED_RUN_IRIS_PACK_VALIDATE_FIRST));
return;
}
for (Map.Entry<String, PackValidationResult> entry : snapshot.entrySet()) {
for (String line : compatOutput(entry.getKey(), entry.getValue())) {
s.sendMessage(line);
}
}
return;
}
for (String line : compatOutput(pack, PackValidationRegistry.get(pack))) {
s.sendMessage(line);
}
}
/**
* The compat listing for one pack, straight from the persisted validation so no pack is reloaded. Every key is
* printed with every subject; the boot listing is the capped form of the same data.
*/
static List<String> compatOutput(String pack, PackValidationResult result) {
List<String> lines = new ArrayList<>();
if (result == null) {
lines.add(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_NO_VALIDATION_RESULT_RUN_IRIS_PACK_VALIDATE,
MessageArgument.untrusted("pack", String.valueOf(pack)),
MessageArgument.untrusted("pack2", String.valueOf(pack))));
return lines;
}
String version = compatVersion(result);
if (result.getCompatFindings().isEmpty()) {
lines.add(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_COMPAT_NONE,
MessageArgument.untrusted("pack", pack),
MessageArgument.untrusted("version", version)));
return lines;
}
lines.add(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_COMPAT_HEADER,
MessageArgument.untrusted("pack", pack),
MessageArgument.untrusted("version", version)));
for (String line : PackCompatReport.of(result.getCompatFindings()).keyLines(0)) {
lines.add(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_MESSAGE_2,
MessageArgument.untrusted("value", line)));
}
lines.add(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_COMPAT_REMEDY));
return lines;
}
/** The live Minecraft version, falling back to the one the validation ran against. */
static String compatVersion(PackValidationResult result) {
if (IrisPlatforms.isBound()) {
String live = IrisPlatforms.get().minecraftVersion();
if (live != null && !live.isBlank()) {
return live;
}
}
String persisted = result.getMinecraftVersion();
return persisted == null || persisted.isBlank() ? "unknown" : persisted;
}
private PackValidationResult runValidate(VolmitSender s, File packFolder) {
try {
PackValidationResult result = PackValidator.validate(packFolder);
PackValidationRegistry.publish(result);
reportResult(s, result);
return result;
} catch (Throwable e) {
Iris.reportError("Pack validation failed for '" + packFolder.getName() + "'", e);
String detail = e.getMessage() == null || e.getMessage().isBlank()
? e.getClass().getSimpleName()
: e.getMessage();
PackValidationResult result = new PackValidationResult(
packFolder.getName(),
List.of("Pack validation failed with " + e.getClass().getSimpleName() + ": " + detail),
List.of(),
System.currentTimeMillis());
PackValidationRegistry.publish(result);
reportResult(s, result);
return result;
}
}
private void persistValidationCache(File packsRoot) {
List<File> packDirectories = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
List<PackValidationResult> results = new ArrayList<>(packDirectories.size());
for (File packDirectory : packDirectories) {
PackValidationResult result = PackValidationRegistry.get(packDirectory.getName());
if (result == null) {
// The boot cache only loads when it covers every pack, so a partial write is
// pointless - but say so instead of silently skipping the persist forever.
Iris.warn("Pack validation cache not written: \"" + packDirectory.getName()
+ "\" has no registered result. Run /iris pack validate all to repopulate it.");
return;
}
results.add(result);
}
try {
PackValidationCache.save(
Iris.instance.getDataFile("cache", "pack-validation.json").toPath(),
PackValidationCache.contentFingerprint(packsRoot),
PackValidationCache.contextFingerprint(),
results);
} catch (IOException | RuntimeException e) {
Iris.reportError("Could not persist refreshed pack-validation results", e);
}
}
private void reportResult(VolmitSender s, PackValidationResult result) {
if (result.isLoadable()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_PACK_IS_LOADABLE_WARNINGS, MessageArgument.untrusted("packName", String.valueOf(result.getPackName())), MessageArgument.untrusted("size", String.valueOf(result.getWarnings().size()))));
} else {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_PACK_IS_BROKEN, MessageArgument.untrusted("packName", String.valueOf(result.getPackName()))));
for (String reason : result.getBlockingErrors()) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_MESSAGE, MessageArgument.untrusted("reason", String.valueOf(reason))));
}
}
int wMax = Math.min(10, result.getWarnings().size());
for (int i = 0; i < wMax; i++) {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_MESSAGE_2, MessageArgument.untrusted("value", String.valueOf(result.getWarnings().get(i)))));
}
if (result.getWarnings().size() > wMax) {
s.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_MORE_WARNING_S,
MessageArgument.trusted("count", result.getWarnings().size() - wMax)
));
}
}
private File findPack(VolmitSender sender, String pack) {
if (pack == null || pack.isBlank()) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_PACK_YOU_MUST_SPECIFY_PACK_NAME));
return null;
}
File packFolder = PackDirectoryResolver.resolveExisting(Iris.instance.getDataFolder("packs"), pack);
if (packFolder == null) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_PACK_PACK_NOT_FOUND_UNDER_PACKS, MessageArgument.untrusted("pack", pack)));
return null;
}
return packFolder;
}
private void reportPaths(VolmitSender sender, List<String> paths, TextKey label) {
int max = Math.min(10, paths.size());
for (int i = 0; i < max; i++) {
sender.sendMessage(IrisLanguage.text(
BukkitCommandMessages.COMMAND_PACK_MESSAGE,
MessageArgument.untrusted("label", IrisLanguage.text(label)),
MessageArgument.untrusted("value", paths.get(i))
));
}
if (paths.size() > max) {
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_PACK_MORE, MessageArgument.untrusted("value", (paths.size() - max))));
}
}
}
@@ -0,0 +1,135 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.localization.IrisMessages;
import art.arcane.iris.Iris;
import art.arcane.iris.studio.view.PregeneratorJob;
import art.arcane.iris.world.pregen.PregenTask;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.localization.C;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.math.Position2;
import org.bukkit.World;
import org.bukkit.util.Vector;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
@Director(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!", descriptionKey = "iris.director.commandpregen.director.pregenerate_your_iris_worlds")
public class CommandPregen implements DirectorExecutor {
@Director(description = "Pregenerate a world", descriptionKey = "iris.director.commandpregen.director.pregenerate_world")
public void start(
@Param(description = "The radius of the pregen in blocks", descriptionKey = "iris.director.commandpregen.param.radius_pregen_blocks", aliases = "size")
int radius,
@Param(description = "The world to pregen", descriptionKey = "iris.director.commandpregen.param.world_pregen", contextual = true, contextualOverride = true)
World world,
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", descriptionKey = "iris.director.commandpregen.param.center_location_pregen_use_me_your_current_location", defaultValue = "0,0")
Vector center,
@Param(description = "Open the Iris pregen gui", descriptionKey = "iris.director.commandpregen.param.open_iris_pregen_gui", defaultValue = "true")
boolean gui,
@Param(name = "serial", description = "Generate only one chunk at a time", descriptionKey = "iris.director.commandpregen.param.generate_only_one_chunk_at_time", defaultValue = "false")
boolean serial
) {
if (radius <= 0) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_PREGEN_RADIUS_MUST_BE_GREATER_THAN_ZERO_BLOCKS));
return;
}
if (serial && !IrisToolbelt.supportsStrictSerialPregeneration()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_STRICT_SERIAL_PREGENERATION_REQUIRES_PAPER_PAPER_COMPATIBLE_SERVER));
return;
}
if (PregeneratorJob.getInstance() != null) {
// Reject like the modded adapter does; never silently kill a running job.
sender().sendMessage(IrisLanguage.text(IrisMessages.PREGEN_ALREADY_RUNNING));
return;
}
try {
if (sender().isPlayer() && access() == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_ENGINE_ACCESS_THIS_WORLD_IS_NULL));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_PLEASE_MAKE_SURE_WORLD_IS_LOADED_ENGINE_IS_INITIALIZED_GENERATE));
}
PregenTask task = PregenTask
.builder()
.center(new Position2(center.getBlockX(), center.getBlockZ()))
.gui(gui)
.radiusX(radius)
.radiusZ(radius)
.build();
if (serial) {
IrisToolbelt.pregenerateSerial(task, world);
} else {
IrisToolbelt.pregenerate(task, world);
}
String msg = C.GREEN + "Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
sender().sendMessage(msg);
Iris.info(msg);
} catch (Throwable e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_FAILED_START_PREGENERATION_SEE_CONSOLE_DETAILS));
Iris.reportError(e);
}
}
@Director(description = "Stop the active pregeneration task", descriptionKey = "iris.director.commandpregen.director.stop_active_pregeneration_task", aliases = "x")
public void stop() {
if (PregeneratorJob.shutdownInstance()) {
String message = C.BLUE + "Pregen stop requested; finishing active work before cancellation.";
sender().sendMessage(message);
Iris.info(message);
} else {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_NO_ACTIVE_PREGENERATION_TASKS_STOP));
}
}
@Director(description = "Pause / continue the active pregeneration task", descriptionKey = "iris.director.commandpregen.director.pause_continue_active_pregeneration_task", aliases = {"resume"})
public void pause() {
if (PregeneratorJob.pauseResume()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_PAUSED_UNPAUSED_PREGENERATION_TASK_NOW, MessageArgument.trusted("value", IrisLanguage.text(PregeneratorJob.isPaused() ? RuntimeUiMessages.STATUS_PAUSED : RuntimeUiMessages.STATUS_RUNNING))));
} else {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_NO_ACTIVE_PREGENERATION_TASKS_PAUSE_UNPAUSE));
}
}
@Director(description = "Show the active pregeneration status", descriptionKey = "iris.director.commandpregen.director.show_active_pregeneration_status")
public void status() {
PregeneratorJob.PregenProgress progress = PregeneratorJob.progressSnapshot();
if (progress == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_NO_ACTIVE_PREGENERATION_TASK));
return;
}
String world = progress.worldName() == null ? "?" : progress.worldName();
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_PREGEN, MessageArgument.untrusted("world", world), MessageArgument.untrusted("value", Form.f(progress.generated())), MessageArgument.untrusted("value2", Form.f(progress.totalChunks())), MessageArgument.untrusted("value3", String.format("%.1f", progress.percent())), MessageArgument.untrusted("value4", (progress.paused() ? C.YELLOW + " PAUSED" : ""))));
sender().sendMessage(IrisLanguage.text(
BukkitCommandMessagesExtended.COMMAND_PREGEN_SPEED_S_ETA_ELAPSED_METHOD,
MessageArgument.untrusted("overall", Form.f(progress.overallChunksPerSecond(), 1)),
MessageArgument.untrusted("tenSecond", Form.f(progress.chunksPerSecond(), 1)),
MessageArgument.untrusted("thirtySecond", Form.f(progress.thirtySecondChunksPerSecond(), 1)),
MessageArgument.untrusted("sixtySecond", Form.f(progress.sixtySecondChunksPerSecond(), 1)),
MessageArgument.untrusted("eta", Form.duration(progress.eta(), 2)),
MessageArgument.untrusted("elapsed", Form.duration(progress.elapsed(), 2)),
MessageArgument.untrusted("method", progress.method()),
MessageArgument.untrusted("failures", progress.failed() > 0 ? C.RED + " Failed: " + Form.f(progress.failed()) : "")));
}
}
@@ -0,0 +1,359 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.generation.cache.AtomicCache;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.bukkit.plugin.IrisService;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.world.task.J;
import art.arcane.volmlib.util.director.DirectorEngineOptions;
import art.arcane.volmlib.util.director.compat.DirectorEngineFactory;
import art.arcane.volmlib.util.director.context.DirectorContextRegistry;
import art.arcane.volmlib.util.director.runtime.DirectorExecutionMode;
import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult;
import art.arcane.volmlib.util.director.runtime.DirectorInvocation;
import art.arcane.volmlib.util.director.runtime.DirectorInvocationHook;
import art.arcane.volmlib.util.director.runtime.DirectorRuntimeEngine;
import art.arcane.volmlib.util.director.runtime.DirectorRuntimeNode;
import art.arcane.volmlib.util.director.runtime.DirectorSender;
import art.arcane.volmlib.util.director.help.DirectorMiniMenu;
import art.arcane.volmlib.util.director.help.DirectorMiniMenu.DirectorHelpPage;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.IrisMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.localization.LanguageAudience;
import art.arcane.volmlib.util.plugin.ComponentMessenger;
public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, DirectorInvocationHook {
private static final String ROOT_COMMAND = "iris";
private static final String ROOT_PERMISSION = "iris.all";
private final ThreadLocal<Deque<LanguageAudience.Scope>> languageScopes = ThreadLocal.withInitial(ArrayDeque::new);
private final transient AtomicCache<DirectorRuntimeEngine> directorCache = new AtomicCache<>();
private final transient ThreadLocal<CommandSender> dispatchSenders = new ThreadLocal<>();
@Override
public void onEnable() {
PluginCommand command = findBukkitCommand();
if (command != null) {
command.setExecutor(this);
command.setTabCompleter(this);
} else {
registerPaperCommand();
}
J.a(this::getDirector);
}
@Override
public void onDisable() {
}
public DirectorRuntimeEngine getDirector() {
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
new CommandIris(),
DirectorEngineOptions.builder()
.contexts(buildDirectorContexts())
.dispatcher(this::dispatchDirector)
.invocationHook(this)
.legacyHandlers(DirectorSystem.handlers)
.textResolver(IrisLanguage.directorResolver())
.build()
));
}
private DirectorContextRegistry buildDirectorContexts() {
DirectorContextRegistry contexts = new DirectorContextRegistry();
contexts.register(CommandSender.class, (invocation, map) -> {
if (invocation.getSender() instanceof BukkitDirectorSender sender) {
return sender.sender();
}
return null;
});
for (Map.Entry<Class<?>, DirectorContextHandler<?>> entry : DirectorContextHandler.contextHandlers.entrySet()) {
registerContextHandler(contexts, entry.getKey(), entry.getValue());
}
return contexts;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void registerContextHandler(DirectorContextRegistry contexts, Class<?> type, DirectorContextHandler<?> handler) {
contexts.register((Class) type, (invocation, map) -> {
if (invocation.getSender() instanceof BukkitDirectorSender sender) {
return ((DirectorContextHandler) handler).handle(new VolmitSender(sender.sender()));
}
return null;
});
}
private void dispatchDirector(DirectorExecutionMode mode, Runnable runnable) {
if (mode != DirectorExecutionMode.SYNC) {
runnable.run();
return;
}
CommandSender sender = dispatchSenders.get();
if (sender instanceof Player player) {
if (!J.runEntity(player, runnable)) {
throw new IllegalStateException("Failed to schedule player command on its entity thread");
}
return;
}
J.s(runnable);
}
@Override
public void beforeInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) {
if (invocation.getSender() instanceof BukkitDirectorSender sender) {
languageScopes.get().push(LanguageAudience.open(sender.sender() instanceof Player player ? player.getUniqueId() : null));
DirectorContext.touch(new VolmitSender(sender.sender()));
}
}
@Override
public void afterInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) {
DirectorContext.remove();
Deque<LanguageAudience.Scope> scopes = languageScopes.get();
if (!scopes.isEmpty()) {
scopes.pop().close();
}
if (scopes.isEmpty()) {
languageScopes.remove();
}
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) {
return List.of();
}
return tabCompleteRoot(sender, alias, args);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) {
return false;
}
executeRoot(sender, label, args);
return true;
}
public void executeRoot(CommandSender sender, String label, String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase("language")) {
Iris.instance.selectLanguage(sender, Arrays.copyOfRange(args, 1, args.length));
return;
}
if (!(args.length > 1 && args[0].equalsIgnoreCase("debug") && args[1].equalsIgnoreCase("dump"))
&& !sender.hasPermission(ROOT_PERMISSION)) {
ComponentMessenger.sendSection(sender, IrisLanguage.text(
IrisMessages.COMMAND_PERMISSION_DENIED,
MessageArgument.trusted("permission", ROOT_PERMISSION)
));
return;
}
J.aBukkit(() -> executeCommand(sender, label, args));
}
public List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
List<String> suggestions = args.length > 0 && args[0].equalsIgnoreCase("language")
? Iris.instance.completeLanguage(sender, Arrays.copyOfRange(args, 1, args.length))
: runDirectorTab(sender, alias, args);
if (sender instanceof Player player && IrisSettings.get().getGeneral().isCommandSounds()) {
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f));
}
return suggestions;
}
private PluginCommand findBukkitCommand() {
try {
return Iris.instance.getCommand(ROOT_COMMAND);
} catch (UnsupportedOperationException ignored) {
return null;
}
}
private void registerPaperCommand() {
try {
Class<?> registrarType = Class.forName(
"art.arcane.iris.command.PaperCommandRegistrar",
true,
getClass().getClassLoader()
);
Method register = registrarType.getDeclaredMethod("register", Iris.class, CommandSVC.class);
register.invoke(null, Iris.instance, this);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new IllegalStateException("Paper command registration failed", cause);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Paper command registrar is unavailable", e);
} catch (LinkageError e) {
throw new IllegalStateException("Paper command APIs are unavailable", e);
}
}
private void executeCommand(CommandSender sender, String label, String[] args) {
if (sendHelpIfRequested(sender, args)) {
playSuccessSound(sender);
return;
}
DirectorExecutionResult result = runDirector(sender, label, args);
if (result.isSuccess()) {
playSuccessSound(sender);
return;
}
playFailureSound(sender);
if (result.getMessage() == null || result.getMessage().trim().isEmpty()) {
new VolmitSender(sender).sendMessage(C.RED + IrisLanguage.text(IrisMessages.COMMAND_UNKNOWN));
}
}
private boolean sendHelpIfRequested(CommandSender sender, String[] args) {
try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) {
Optional<DirectorHelpPage> request = DirectorMiniMenu.resolveHelp(getDirector(), Arrays.asList(args));
if (request.isEmpty()) {
return false;
}
DirectorMiniMenu.deliver(
sender,
request.get(),
DirectorMiniMenu.Theme.irisGreen(),
IrisLanguage.directorResolver()
);
return true;
}
}
private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) {
dispatchSenders.set(sender);
DirectorContext.touch(new VolmitSender(sender));
try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) {
return getDirector().execute(new DirectorInvocation(new BukkitDirectorSender(sender), label, Arrays.asList(args)));
} catch (Throwable e) {
Iris.warn("Director command execution failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
return DirectorExecutionResult.notHandled();
} finally {
DirectorContext.remove();
dispatchSenders.remove();
}
}
private List<String> runDirectorTab(CommandSender sender, String alias, String[] args) {
DirectorContext.touch(new VolmitSender(sender));
try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) {
return getDirector().tabComplete(new DirectorInvocation(new BukkitDirectorSender(sender), alias, Arrays.asList(args)));
} catch (Throwable e) {
Iris.warn("Director tab completion failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
return List.of();
} finally {
DirectorContext.remove();
}
}
private void playFailureSound(CommandSender sender) {
if (!IrisSettings.get().getGeneral().isCommandSounds()) {
return;
}
if (sender instanceof Player player) {
J.runEntity(player, () -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f,
Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f));
}
}
private void playSuccessSound(CommandSender sender) {
if (!IrisSettings.get().getGeneral().isCommandSounds()) {
return;
}
if (sender instanceof Player player) {
J.runEntity(player, () -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f,
Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f));
}
}
private static final java.util.concurrent.atomic.AtomicBoolean SOUND_LOGGED = new java.util.concurrent.atomic.AtomicBoolean(false);
private void playSounds(Player player, Sound a, float av, float ap, Sound b, float bv, float bp) {
try {
player.playSound(player.getLocation(), a, av, ap);
player.playSound(player.getLocation(), b, bv, bp);
} catch (Throwable e) {
if (SOUND_LOGGED.compareAndSet(false, true)) {
try {
java.io.File f = Iris.instance.getDataFile("adventure-debug.txt");
java.nio.file.Files.writeString(f.toPath(), "SOUND FAIL: " + e.getClass().getName() + ": " + e.getMessage() + "\n", java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND);
} catch (Throwable ignored) {
}
}
}
}
private record BukkitDirectorSender(CommandSender sender) implements DirectorSender {
@Override
public String getName() {
return sender.getName();
}
@Override
public boolean isPlayer() {
return sender instanceof Player;
}
@Override
public void sendMessage(String message) {
if (message != null && !message.trim().isEmpty()) {
ComponentMessenger.sendLiteral(sender, message);
}
}
}
}

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