├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── FEATURES.md ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── settings.gradle └── src └── main ├── java └── fi │ └── dy │ └── masa │ └── servux │ ├── Reference.java │ ├── Servux.java │ ├── commands │ ├── CommandProvider.java │ ├── ICommandProvider.java │ └── ServuxCommand.java │ ├── dataproviders │ ├── DataProviderBase.java │ ├── DataProviderManager.java │ ├── DebugDataProvider.java │ ├── EntitiesDataProvider.java │ ├── HudDataProvider.java │ ├── IDataProvider.java │ ├── LitematicsDataProvider.java │ ├── ServuxConfigProvider.java │ ├── StructureDataProvider.java │ └── TweaksDataProvider.java │ ├── event │ ├── PlayerHandler.java │ ├── ServerHandler.java │ └── ServerInitHandler.java │ ├── interfaces │ ├── IPlayerListener.java │ ├── IPlayerManager.java │ ├── IServerCommand.java │ ├── IServerInitDispatcher.java │ ├── IServerInitHandler.java │ ├── IServerListener.java │ └── IServerManager.java │ ├── loggers │ ├── DataLogger.java │ ├── DataLoggerBase.java │ ├── DataLoggerMobCaps.java │ ├── DataLoggerTPS.java │ ├── DataLoggerType.java │ └── data │ │ ├── MobCapData.java │ │ └── TPSData.java │ ├── mixin │ ├── MixinMain.java │ ├── block │ │ ├── MixinBlock_UpdateSuppression.java │ │ ├── MixinChestBlock.java │ │ ├── MixinHopperBlockEntity.java │ │ ├── MixinRailBlocks.java │ │ └── MixinStairsBlock.java │ ├── debug │ │ ├── IMixinMobEntity.java │ │ ├── MixinDebugInfoSender.java │ │ ├── MixinPath.java │ │ └── MixinSharedConstants.java │ ├── entity │ │ ├── MixinAllayEntity.java │ │ ├── MixinItemEntity.java │ │ └── MixinMobEntity.java │ ├── item │ │ ├── MixinBlockItem_EasyPlace.java │ │ └── MixinItemStack.java │ ├── nbt │ │ ├── IMixinNbtReadView.java │ │ └── IMixinNbtWriteView.java │ ├── network │ │ ├── MixinServerPlayNetworkHandler_EasyPlace.java │ │ └── MixinServerPlayNetworkHandler_QueryNbt.java │ ├── server │ │ ├── IMixinServerTickManager.java │ │ ├── MixinCommandManager.java │ │ ├── MixinMinecraftDedicatedServer.java │ │ ├── MixinMinecraftServer.java │ │ └── MixinPlayerManager.java │ └── world │ │ ├── IMixinWorldTickScheduler.java │ │ ├── MixinServerChunkLoadingManager.java │ │ ├── MixinServerWorld.java │ │ ├── MixinWorldChunk_UpdateSuppression.java │ │ └── MixinWorld_UpdateSuppression.java │ ├── network │ ├── IPluginServerPlayHandler.java │ ├── IServerPayloadData.java │ ├── IServerPlayHandler.java │ ├── PacketSplitter.java │ ├── ServerPlayHandler.java │ └── packet │ │ ├── ServuxDebugHandler.java │ │ ├── ServuxDebugPacket.java │ │ ├── ServuxEntitiesHandler.java │ │ ├── ServuxEntitiesPacket.java │ │ ├── ServuxHudHandler.java │ │ ├── ServuxHudPacket.java │ │ ├── ServuxLitematicaHandler.java │ │ ├── ServuxLitematicaPacket.java │ │ ├── ServuxStructuresHandler.java │ │ ├── ServuxStructuresPacket.java │ │ ├── ServuxTweaksHandler.java │ │ └── ServuxTweaksPacket.java │ ├── schematic │ ├── LitematicaSchematic.java │ ├── SchematicMetadata.java │ ├── SchematicSchema.java │ ├── container │ │ ├── ILitematicaBlockStatePalette.java │ │ ├── ILitematicaBlockStatePaletteResizer.java │ │ ├── LitematicaBitArray.java │ │ ├── LitematicaBlockStateContainer.java │ │ ├── LitematicaBlockStatePaletteHashMap.java │ │ └── LitematicaBlockStatePaletteLinear.java │ ├── conversion │ │ └── SchematicConversionMaps.java │ ├── placement │ │ ├── SchematicPlacement.java │ │ └── SubRegionPlacement.java │ ├── selection │ │ ├── AreaSelection.java │ │ ├── AreaSelectionSimple.java │ │ ├── Box.java │ │ ├── BoxSliced.java │ │ ├── SelectionManager.java │ │ └── SelectionMode.java │ └── transmit │ │ ├── SchematicBuffer.java │ │ └── SchematicBufferManager.java │ ├── servux │ ├── PlayerListener.java │ ├── ServerListener.java │ └── ServuxInitHandler.java │ ├── settings │ ├── AbstractServuxSetting.java │ ├── IServuxSetting.java │ ├── IServuxSettingCallback.java │ ├── ServuxBoolSetting.java │ ├── ServuxIntSetting.java │ ├── ServuxListSetting.java │ ├── ServuxStringListSetting.java │ └── ServuxStringSetting.java │ └── util │ ├── BlockUtils.java │ ├── EntityUtils.java │ ├── FileUtils.java │ ├── IWorldUpdateSuppressor.java │ ├── IntBoundingBox.java │ ├── InventoryUtils.java │ ├── JsonUtils.java │ ├── LayerMode.java │ ├── LayerRange.java │ ├── PasteLayerBehavior.java │ ├── PlacementHandler.java │ ├── PlayerDimensionPosition.java │ ├── ReplaceBehavior.java │ ├── SchematicPlacingUtils.java │ ├── StringUtils.java │ ├── Timeout.java │ ├── WorldUtils.java │ ├── data │ ├── Constants.java │ ├── FileType.java │ ├── ResourceLocation.java │ └── Schema.java │ ├── i18nLang.java │ ├── nbt │ ├── NbtUtils.java │ └── NbtView.java │ └── position │ └── PositionUtils.java └── resources ├── assets └── servux │ └── lang │ ├── en_us.json │ ├── ko_kr.json │ ├── uk_ua.json │ └── zh_cn.json ├── fabric.mod.json └── mixins.servux.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # text stuff 2 | * text=auto 3 | *.bat text eol=crlf 4 | *.groovy text eol=lf 5 | *.java text eol=crlf 6 | *.md text 7 | *.properties text eol=lf 8 | *.scala text eol=lf 9 | *.sh text eol=lf 10 | .gitattributes text eol=lf 11 | .gitignore text eol=lf 12 | build.gradle text eol=lf 13 | gradlew text eol=lf 14 | gradle/wrapper/gradle-wrapper.properties text eol=crlf 15 | COPYING.txt text eol=lf 16 | COPYING.LESSER.txt text eol=lf 17 | README.md text eol=lf 18 | 19 | #binary 20 | *.dat binary 21 | *.bin binary 22 | *.png binary 23 | *.exe binary 24 | *.dll binary 25 | *.zip binary 26 | *.jar binary 27 | *.7z binary 28 | *.db binary 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [ pull_request, push ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | # Use these Java versions 14 | java: [ 21 ] 15 | distro: [ temurin ] 16 | # and run on both Linux and Windows 17 | os: [ ubuntu-latest ] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: checkout repository 21 | uses: actions/checkout@v4 22 | - name: validate gradle wrapper 23 | uses: gradle/actions/wrapper-validation@v4 24 | - name: setup jdk ${{ matrix.java }} 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: ${{ matrix.distro }} 28 | java-version: ${{ matrix.java }} 29 | - name: make gradle wrapper executable 30 | if: ${{ runner.os != 'Windows' }} 31 | run: chmod +x ./gradlew 32 | - name: build 33 | run: ./gradlew build 34 | - name: capture build artifacts 35 | if: ${{ runner.os == 'Linux' }} 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: Artifacts 39 | path: build/libs/ 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | bin/ 4 | build/ 5 | eclipse/ 6 | logs/ 7 | .classpath 8 | .project 9 | build.number 10 | libs/ 11 | .idea/ 12 | run/ 13 | -------------------------------------------------------------------------------- /FEATURES.md: -------------------------------------------------------------------------------- 1 | Servux New Features (0.3.7+) 2 | ============================ 3 | 4 | ## New Data Provider changes since Servux 0.1.0: 5 | * `DataProviderToggles` in `servux.json` now can actually enable/disable data providers. `servux.json` file is now usable (in 0.2.0 and lower; this was not possible). So yes; you can technically disable the `servux_main` data provider; and then lose the ability to use those features, or fix it without replacing the config file. 6 | * `servux_main` - Core Servux-based service that manages the `servux.json` file and the `/servux` command management. 7 | * Provides backend setting for `permission_level_easy_place` --> Adds the `Easy Place V3` server-side backed for Litematica and Tweakeroo. 8 | * Provides backend setting for `default_language` & `debug_log`. 9 | * `hud_data` - Provides MiniHUD with server side data for any misc, and Info Line / HUD data. It can be activated by the `Generic` -> `hudDataSync` toggle. It can provide: 10 | * Spawn Chunk Radius / Spawn Position. Shared upon metadata handshake, or future changes to the spawn metadata; such as when someone changes the world spawn location. 11 | * Weather Info `share_weather_status` and related `update_interval` for tick rate limiting; and has a separate permission node. 12 | * World Seed `share_seed`. Only shared upon Metadata handshake, and has a separate permission node. 13 | * (1.21.2+) ServerRecipeBook data dump (For Use with FurnaceXP Info Line); only sent upon request, and normally at server login after the metadata handshake. 14 | * `entity_data` - Provides MiniHUD with entity/tile entity NBT information for various systems such as `inventoryPreview`, various Renderers, and various Info Lines. It can be activated by `Generic` -> `entityDataSync`. 15 | * Provides backend setting for `nbtQueryOverride` where you can offer an alternative OP permission level for Vanilla `NbtQuery` packets. 16 | * `litematic_data` - Provides Litematica with entity/tile entity NBT information for use with `InfoOverlay`; and also provides Litematic saving and pasting services. It can be activated by `Generic` -> `entityDataSync`. 17 | * Provides backend setting for `fix_rail_rotations` && `fix_stairs_mirror`. 18 | * Litematic Paste operations has a separate permissions node. 19 | * `tweaks_data` - Provides Tweakeroo with entity/tile entity NBT information for `inventoryPreview`. Can be expanded in the future to support more advanced Tweaks. It can be activated by enabling `tweakServerDataSync`. 20 | * `debug_data` (_Disabled by default_) - Provides MiniHUD with the server-side data for the Vanilla debug rendering. It can be activated by `RenderToggle` -> `debugDataMainToggle`. 21 | * _**NOTE**_: All Data Providers also has their related `permission` configs for controlling OP level style permissions. All Data Providers and permissions are also compatible with Luck Permissions API. 22 | * Example Luck Permissions API node: `servux.provider.entity_data.nbt_query_override`. 23 | 24 | ## `/servux` Command reference: 25 | * `reload` -- Reloads the config file, discarding the existing config in memory. 26 | * `save` -- Forces a save of the config file, discarding the existing file; and overwriting it with the configuration in memory. 27 | * `set` [setting] [value] -- Sets a configuration [value] for the [setting]. 28 | * `info` [setting] -- Displays the current configuration for the [setting]. 29 | * `list` [dataprovider] -- Lists all settings and their respective values. Can be limited to a specific [dataprovider]. 30 | * `search` [pattern] --- Lists all settings matching the search [pattern]. 31 | * All config settings can be clicked upon to auto-complete a `set` command; after using `info`, `list` or `search`; similar to how the `/carpet` command works. 32 | * Available settings are modularized per their respective [dataprovider]. 33 | * All `/servux` command text can be translated using the available i18n language files. Currently only English `en_us` and Chinese (Traditional) `zh_cn` is available, but more may become available as people offer translation assistance. If you wish to contribute translations; please visit https://translate.sakuraryoko.com -- and if you need a language file added; please contact me. 34 | 35 | ## Default Config File: 36 | * File is loaded / saved upon Server start, or Vanilla data pack `/reload` command. 37 | ```json 38 | { 39 | "DataProviderToggles": { 40 | "hud_data": true, 41 | "litematic_data": true, 42 | "structure_bounding_boxes": true, 43 | "servux_main": true, 44 | "tweaks_data": true, 45 | "entity_data": true, 46 | "debug_data": false 47 | }, 48 | "hud_data": { 49 | "permission_level": 0, 50 | "update_interval": 80, 51 | "share_weather_status": false, 52 | "weather_permission_level": 0, 53 | "share_seed": false, 54 | "seed_permission_level": 2 55 | }, 56 | "litematic_data": { 57 | "permission_level": 0, 58 | "permission_level_paste": 0, 59 | "fix_rail_rotations": true, 60 | "fix_stairs_mirror": true 61 | }, 62 | "structure_bounding_boxes": { 63 | "permission_level": 0, 64 | "structures_blacklist_enabled": false, 65 | "structures_whitelist_enabled": false, 66 | "structures_blacklist": [ 67 | "minecraft:buried_treasure" 68 | ], 69 | "structures_whitelist": [], 70 | "update_interval": 40, 71 | "timeout": 600 72 | }, 73 | "servux_main": { 74 | "permission_level": 0, 75 | "permission_level_admin": 3, 76 | "permission_level_easy_place": 0, 77 | "default_language": "en_us", 78 | "debug_log": false 79 | }, 80 | "tweaks_data": { 81 | "permission_level": 0 82 | }, 83 | "entity_data": { 84 | "permission_level": 0, 85 | "nbt_query_override": false, 86 | "nbt_query_permission_level": 2 87 | }, 88 | "debug_data": { 89 | "permission_level": 2 90 | } 91 | } 92 | ``` 93 | 94 | ## Future plans: 95 | * Add Syncmatica-like protocol for Litematica. (new data provider) 96 | * Add Shulker Box stacking (tweaks_data) 97 | * Add Mob Caps Data (hud_data) 98 | * Add TPS / MSPT data (hud_data) 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://jitpack.io/v/sakura-ryoko/servux.svg)](https://jitpack.io/#sakura-ryoko/servux) 2 | 3 | Servux 4 | ============== 5 | Servux is a server-side mod that provides extra support/features for some client-side mods when playing on a server. 6 | 7 | **Servux itself is never needed on the clients or in single player**, 8 | it's only needed/useful on the dedicated server side in multiplayer. 9 | 10 | In version 0.1.x it only has one thing, which is sending structure bounding boxes for MiniHUD so that it can render those also in multiplayer. 11 | 12 | For compiled builds (= downloads), see https://www.curseforge.com/minecraft/mc-mods/servux 13 | 14 | Compiling 15 | ========= 16 | * Clone the repository 17 | * Open a command prompt/terminal to the repository directory 18 | * run 'gradlew build' 19 | * The built jar file will be in build/libs/ 20 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.11-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | repositories { 7 | // maven { url = 'https://masa.dy.fi/maven' } 8 | maven { url = 'https://maven.fallenbreath.me/releases' } 9 | maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } 10 | // maven { url = 'https://maven.nucleoid.xyz/' } 11 | // maven { url = 'https://jitpack.io' } 12 | } 13 | 14 | dependencies { 15 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 16 | mappings "net.fabricmc:yarn:${project.mappings_version}:v2" 17 | modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" 18 | implementation "com.google.code.findbugs:jsr305:3.0.2" 19 | 20 | // Fabric API. This is technically optional, but you probably want it anyway. 21 | //modCompile "net.fabricmc.fabric-api:fabric-api:" + project.fabric_version 22 | include(modApi(fabricApi.module("fabric-api-base", project.fabric_api_version))) 23 | include(modApi(fabricApi.module("fabric-networking-api-v1", project.fabric_api_version))) 24 | 25 | // Lucko Permissions API 26 | modImplementation include("me.lucko:fabric-permissions-api:${project.fabric_permissions_api_version}") 27 | // modRuntimeOnly 'me.fallenbreath:mixin-auditor:0.1.0' 28 | } 29 | 30 | base { 31 | group = project.group + "." + project.mod_id 32 | archivesName = project.mod_file_name + '-' + project.minecraft_version_out 33 | version = project.mod_version 34 | 35 | if (version.endsWith('-dev')) { 36 | version += "." + new Date().format('yyyyMMdd.HHmmss') 37 | } 38 | } 39 | 40 | //loom { 41 | // def commonVmArgs = ['-Dmixin.debug.export=true', '-Dmixin.debug.countInjections=true'] 42 | // // [FEATURE] MIXIN_AUDITOR 43 | // runs { 44 | // def auditVmArgs = [*commonVmArgs, '-DmixinAuditor.audit=true'] 45 | // serverMixinAudit { 46 | // server() 47 | // vmArgs auditVmArgs 48 | // ideConfigGenerated false 49 | // } 50 | // } 51 | //} 52 | 53 | processResources { 54 | // Exclude the GIMP image files 55 | exclude '**/*.xcf' 56 | exclude '**/xcf' 57 | 58 | // this will ensure that this task is redone when the versions change. 59 | //inputs.property "minecraft_version", project.project.minecraft_version 60 | 61 | inputs.property "mod_version", project.mod_version 62 | 63 | filesMatching("fabric.mod.json") { 64 | expand "mod_version": project.mod_version 65 | } 66 | } 67 | 68 | tasks.withType(JavaCompile).configureEach { 69 | // ensure that the encoding is set to UTF-8, no matter what the system default is 70 | // this fixes some edge cases with special characters not displaying correctly 71 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 72 | // If Javadoc is generated, this must be specified in that task too. 73 | it.options.encoding = "UTF-8" 74 | 75 | // Minecraft 1.20.5 (24w14a) upwards uses Java 21. 76 | it.options.release = 21 77 | } 78 | 79 | tasks.withType(AbstractArchiveTask).configureEach { 80 | preserveFileTimestamps = true 81 | //reproducibleFileOrder = true 82 | } 83 | 84 | java { 85 | sourceCompatibility = JavaVersion.VERSION_21 86 | targetCompatibility = JavaVersion.VERSION_21 87 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 88 | // if it is present. 89 | // If you remove this line, sources will not be generated. 90 | withSourcesJar() 91 | } 92 | 93 | jar { 94 | from("LICENSE") { 95 | rename { "${it}_${base.archivesName}"} 96 | } 97 | } 98 | 99 | tasks.publish.dependsOn build 100 | publishing { 101 | publications { 102 | mavenJava(MavenPublication) { 103 | // How to introduce archivesName here without it breaking? 104 | artifactId = base.archivesName.get() 105 | 106 | // add all the jars that should be included when publishing to maven 107 | //artifact(jar) { builtBy remapJar } 108 | from components.java 109 | } 110 | } 111 | 112 | repositories { 113 | maven { 114 | url = "$projectDir/../../CommonMaven" 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs = -Xmx3G 2 | org.gradle.daemon = false 3 | org.gradle.cache.cleanup = false 4 | 5 | group = fi.dy.masa 6 | mod_id = servux 7 | mod_name = Servux 8 | author = masa 9 | mod_file_name = servux-fabric 10 | 11 | # Current mod version 12 | mod_version = 0.8.2 13 | 14 | # Minecraft, Fabric Loader and API and mappings versions 15 | minecraft_version_out = 1.21.10 16 | minecraft_version = 1.21.10 17 | mappings_version = 1.21.10+build.2 18 | 19 | fabric_loader_version = 0.17.3 20 | fabric_api_version = 0.136.0+1.21.10 21 | 22 | # Luck Permissions API 23 | fabric_permissions_api_version = 0.4.1 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakura-ryoko/servux/36f6219d9cef155854b02cb2257bd758f93b4560/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sdk install java 21.0.7-tem 3 | - sdk use java 21.0.7-tem 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/Reference.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux; 2 | 3 | import fi.dy.masa.servux.util.StringUtils; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | import net.minecraft.SharedConstants; 6 | 7 | import java.nio.file.Path; 8 | 9 | public class Reference 10 | { 11 | public static final String MOD_ID = "servux"; 12 | public static final String MOD_NAME = "Servux"; 13 | public static final String MOD_VERSION = StringUtils.getModVersionString(MOD_ID); 14 | public static final String MC_VERSION = SharedConstants.getGameVersion().id(); 15 | public static final String MOD_TYPE = "fabric"; 16 | public static final String MOD_STRING = MOD_ID + "-" + MOD_TYPE + "-" + MC_VERSION + "-" + MOD_VERSION; 17 | public static final boolean DEV_DEBUG = false; 18 | 19 | public static final Path DEFAULT_RUN_DIR = FabricLoader.getInstance().getGameDir(); 20 | public static final Path DEFAULT_CONFIG_DIR = FabricLoader.getInstance().getConfigDir(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/Servux.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import fi.dy.masa.servux.commands.CommandProvider; 7 | import fi.dy.masa.servux.commands.ServuxCommand; 8 | import fi.dy.masa.servux.dataproviders.ServuxConfigProvider; 9 | import fi.dy.masa.servux.event.ServerInitHandler; 10 | import fi.dy.masa.servux.servux.ServuxInitHandler; 11 | 12 | public class Servux implements ModInitializer 13 | { 14 | public static final Logger LOGGER = LogManager.getLogger(Reference.MOD_ID); 15 | 16 | @Override 17 | public void onInitialize() 18 | { 19 | ServerInitHandler.getInstance().registerServerInitHandler(new ServuxInitHandler()); 20 | CommandProvider.getInstance().registerCommand(new ServuxCommand()); 21 | // Command Manager gets called before the Init Manager onServerInit() 22 | } 23 | 24 | public static void debugLog(String msg, Object... args) 25 | { 26 | if (ServuxConfigProvider.INSTANCE.hasDebugMode()) 27 | { 28 | LOGGER.info(msg, args); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/commands/CommandProvider.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.commands; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.jetbrains.annotations.ApiStatus; 6 | import com.mojang.brigadier.CommandDispatcher; 7 | import net.minecraft.command.CommandRegistryAccess; 8 | import net.minecraft.server.command.CommandManager; 9 | import net.minecraft.server.command.ServerCommandSource; 10 | import fi.dy.masa.servux.interfaces.IServerCommand; 11 | 12 | public class CommandProvider implements ICommandProvider 13 | { 14 | private static final CommandProvider INSTANCE = new CommandProvider(); 15 | private final List commands = new ArrayList<>(); 16 | public static ICommandProvider getInstance() { return INSTANCE; } 17 | 18 | @Override 19 | public void registerCommand(IServerCommand command) 20 | { 21 | if (!this.commands.contains(command)) 22 | { 23 | this.commands.add(command); 24 | } 25 | } 26 | 27 | @Override 28 | public void unregisterCommand(IServerCommand command) 29 | { 30 | this.commands.remove(command); 31 | } 32 | 33 | @ApiStatus.Internal 34 | public void registerCommands(CommandDispatcher dispatcher, 35 | CommandRegistryAccess registryAccess, 36 | CommandManager.RegistrationEnvironment environment) 37 | { 38 | this.commands.forEach((command) -> command.register(dispatcher, registryAccess, environment)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/commands/ICommandProvider.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.commands; 2 | 3 | import fi.dy.masa.servux.interfaces.IServerCommand; 4 | 5 | public interface ICommandProvider 6 | { 7 | void registerCommand(IServerCommand command); 8 | void unregisterCommand(IServerCommand command); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/dataproviders/DataProviderBase.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.dataproviders; 2 | 3 | import java.util.List; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | 7 | import net.minecraft.util.Identifier; 8 | 9 | import fi.dy.masa.servux.settings.IServuxSetting; 10 | 11 | public abstract class DataProviderBase implements IDataProvider 12 | { 13 | protected final Identifier networkChannel; 14 | protected final String name; 15 | protected final String permNode; 16 | protected final String description; 17 | protected final int protocolVersion; 18 | protected final int defaultPerm; 19 | protected boolean enabled; 20 | protected boolean playRegistered; 21 | private int tickRate = 40; 22 | 23 | protected DataProviderBase(String name, Identifier channel, int protocolVersion, int defaultPerm, String permNode, String description) 24 | { 25 | this.name = name; 26 | this.networkChannel = channel; 27 | this.protocolVersion = protocolVersion; 28 | this.defaultPerm = defaultPerm > -1 && defaultPerm < 5 ? defaultPerm : 0; 29 | this.permNode = permNode; 30 | this.description = description; 31 | } 32 | 33 | @Override 34 | public String getName() 35 | { 36 | return this.name; 37 | } 38 | 39 | @Override 40 | public String getDescription() 41 | { 42 | return this.description; 43 | } 44 | 45 | @Override 46 | public Identifier getNetworkChannel() 47 | { 48 | return this.networkChannel; 49 | } 50 | 51 | @Override 52 | public int getProtocolVersion() 53 | { 54 | return this.protocolVersion; 55 | } 56 | 57 | @Override 58 | public boolean isEnabled() 59 | { 60 | return this.enabled; 61 | } 62 | 63 | @Override 64 | public void setEnabled(boolean enabled) 65 | { 66 | this.enabled = enabled; 67 | } 68 | 69 | @Override 70 | public boolean isRegistered() 71 | { 72 | return this.playRegistered; 73 | } 74 | 75 | @Override 76 | public void setRegistered(boolean toggle) 77 | { 78 | this.playRegistered = toggle; 79 | } 80 | 81 | protected void setTickRate(int tickRate) 82 | { 83 | this.tickRate = Math.max(tickRate, 1); 84 | } 85 | 86 | @Override 87 | public int getTickInterval() 88 | { 89 | return this.tickRate; 90 | } 91 | 92 | @Override 93 | public List> getSettings() 94 | { 95 | return List.of(); 96 | } 97 | 98 | @Override 99 | public JsonObject toJson() 100 | { 101 | JsonObject object = new JsonObject(); 102 | 103 | for (IServuxSetting setting : getSettings()) 104 | { 105 | object.add(setting.name(), setting.writeToJson()); 106 | } 107 | 108 | return object; 109 | } 110 | 111 | @Override 112 | public void fromJson(JsonObject obj) 113 | { 114 | for (IServuxSetting setting : getSettings()) 115 | { 116 | JsonElement element = obj.get(setting.name()); 117 | if (element != null) 118 | { 119 | setting.readFromJson(element); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/dataproviders/IDataProvider.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.dataproviders; 2 | 3 | import com.google.gson.JsonObject; 4 | import fi.dy.masa.servux.settings.IServuxSetting; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.server.network.ServerPlayerEntity; 7 | import net.minecraft.util.Identifier; 8 | import net.minecraft.util.profiler.Profiler; 9 | 10 | import fi.dy.masa.servux.network.IPluginServerPlayHandler; 11 | 12 | import java.util.List; 13 | 14 | public interface IDataProvider 15 | { 16 | /** 17 | * Returns the simple name for this data provider. 18 | * This should preferably be a lower case alphanumeric string with no 19 | * other special characters than '-' and '_'. 20 | * This name will be used in the enable/disable commands as the argument 21 | * and also as the config file key/identifier. 22 | * @return 23 | */ 24 | String getName(); 25 | 26 | /** 27 | * Returns the description of this data provider. 28 | * Used in the command to list the available providers and to check the status 29 | * of a given provider. 30 | * @return 31 | */ 32 | String getDescription(); 33 | 34 | /** 35 | * Returns the network channel name used by this data provider to listen 36 | * for incoming data requests and to respond and send the requested data. 37 | * @return 38 | */ 39 | Identifier getNetworkChannel(); 40 | 41 | /** 42 | * Returns the current protocol version this provider supports 43 | * @return 44 | */ 45 | int getProtocolVersion(); 46 | 47 | /** 48 | * Returns true if this data provider is currently enabled. 49 | * @return 50 | */ 51 | boolean isEnabled(); 52 | 53 | /** 54 | * Enables or disables this data provider 55 | * @param enabled 56 | */ 57 | void setEnabled(boolean enabled); 58 | 59 | /** 60 | * Returns true if this data provider is currently Play Registered, 61 | * This is meant to stop Servux from trying to re-register the Play Channel 62 | * @return 63 | */ 64 | boolean isRegistered(); 65 | 66 | /** 67 | * Marks this Data Provider as Play Registered. 68 | * This is meant to stop Servux from trying to re-register the Play Channel 69 | * @param toggle 70 | */ 71 | void setRegistered(boolean toggle); 72 | 73 | /** 74 | * Informs this data provider that the server has started and should be waiting for requests 75 | * @return 76 | */ 77 | void registerHandler(); 78 | 79 | /** 80 | * Informs this data provider that the server has stopped and should no longer process any data 81 | * @return 82 | */ 83 | void unregisterHandler(); 84 | 85 | /** 86 | * Returns whether or not this data provider should get ticked to periodically send some data, 87 | * or if it's only listening for incoming requests and responds to them directly. 88 | * @return 89 | */ 90 | default boolean shouldTick() 91 | { 92 | return false; 93 | } 94 | 95 | /** 96 | * Returns the interval in game ticks that this data provider should be ticked at 97 | * @return 98 | */ 99 | int getTickInterval(); 100 | 101 | /** 102 | * Called at the given tick rate 103 | * @param server 104 | * @param tickCounter The current server tick (since last server start) 105 | */ 106 | default void tick(MinecraftServer server, int tickCounter, Profiler profiler) 107 | { 108 | } 109 | 110 | /** 111 | * Returns the network packet handler used for this data provider. 112 | * @return () 113 | */ 114 | default IPluginServerPlayHandler getPacketHandler() { return null; } 115 | 116 | /** 117 | * Return if this player is registered to use this Data Provider 118 | * @param player (Player to be checked) 119 | * @return (True|False) 120 | */ 121 | boolean isPlayerRegistered(ServerPlayerEntity player); 122 | 123 | /** 124 | * Determine if Player has permissions to this Data Provider 125 | * @param player (Player to test permissions for) 126 | * @return (true|false) 127 | */ 128 | boolean hasPermission(ServerPlayerEntity player); 129 | 130 | /** 131 | * Signal The Data Providers when the server is shutting down 132 | * (Pre / Post) 133 | */ 134 | void onTickEndPre(); 135 | void onTickEndPost(); 136 | 137 | /** 138 | * Config file handling 139 | * @return 140 | */ 141 | JsonObject toJson(); 142 | 143 | void fromJson(JsonObject obj); 144 | 145 | List> getSettings(); 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/dataproviders/ServuxConfigProvider.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.dataproviders; 2 | 3 | import java.util.List; 4 | import me.lucko.fabric.api.permissions.v0.Permissions; 5 | 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; 8 | import net.minecraft.server.command.ServerCommandSource; 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | import net.minecraft.text.Text; 11 | import net.minecraft.util.Identifier; 12 | 13 | import fi.dy.masa.servux.Reference; 14 | import fi.dy.masa.servux.settings.IServuxSetting; 15 | import fi.dy.masa.servux.settings.ServuxBoolSetting; 16 | import fi.dy.masa.servux.settings.ServuxIntSetting; 17 | import fi.dy.masa.servux.settings.ServuxStringSetting; 18 | import fi.dy.masa.servux.util.StringUtils; 19 | import fi.dy.masa.servux.util.i18nLang; 20 | 21 | public class ServuxConfigProvider extends DataProviderBase 22 | { 23 | public static final ServuxConfigProvider INSTANCE = new ServuxConfigProvider(); 24 | 25 | private final ServuxIntSetting basePermissionLevel = new ServuxIntSetting(this, "permission_level", 0, 4, 0); 26 | private final ServuxIntSetting adminPermissionLevel = new ServuxIntSetting(this, "permission_level_admin", 3, 4, 0); 27 | private final ServuxIntSetting easyPlacePermissionLevel = new ServuxIntSetting(this, "permission_level_easy_place", 0, 4, 0); 28 | private final ServuxBoolSetting easyPlaceValidatorEnabled = new ServuxBoolSetting(this, "easy_place_validator_enabled", true); 29 | private final ServuxStringSetting defaultLanguage = new ServuxStringSetting(this, "default_language", 30 | i18nLang.DEFAULT_LANG, 31 | List.of("en_us", "zh_cn"), false) 32 | { 33 | @Override 34 | public void setValueNoCallback(String value) 35 | { 36 | i18nLang.tryLoadLanguage(value.toLowerCase()); 37 | super.setValueNoCallback(value.toLowerCase()); 38 | } 39 | 40 | @Override 41 | public void setValue(String value) throws CommandSyntaxException 42 | { 43 | String lowerCase = value.toLowerCase(); 44 | if (i18nLang.tryLoadLanguage(lowerCase)) 45 | { 46 | var oldValue = this.getValue(); 47 | super.setValueNoCallback(lowerCase); 48 | this.onValueChanged(oldValue, value); 49 | } 50 | else 51 | { 52 | throw new SimpleCommandExceptionType(StringUtils.translate("servux.command.config.invalid_language", value)).create(); 53 | } 54 | } 55 | }; 56 | private final ServuxBoolSetting debugLog = new ServuxBoolSetting(this, "debug_log", Text.of("Debug Log"), Text.of("Enable debug logging"), false); 57 | private final List> settings = List.of( 58 | this.basePermissionLevel, this.adminPermissionLevel, 59 | this.easyPlacePermissionLevel, this.easyPlaceValidatorEnabled, 60 | this.defaultLanguage, this.debugLog 61 | ); 62 | 63 | protected ServuxConfigProvider() 64 | { 65 | super("servux_main", 66 | Identifier.of("servux:main"), 67 | 1, 0, Reference.MOD_ID+".main", 68 | "The Servux Main configuration data provider"); 69 | } 70 | 71 | @Override 72 | public List> getSettings() 73 | { 74 | return settings; 75 | } 76 | 77 | @Override 78 | public void registerHandler() 79 | { 80 | // NO-OP 81 | } 82 | 83 | @Override 84 | public void unregisterHandler() 85 | { 86 | // NO-OP 87 | } 88 | 89 | @Override 90 | public boolean isPlayerRegistered(ServerPlayerEntity player) 91 | { 92 | return true; 93 | } 94 | 95 | public void doReloadConfig(ServerCommandSource source) 96 | { 97 | DataProviderManager.INSTANCE.readFromConfig(); 98 | source.sendFeedback(() -> StringUtils.translate("servux.command.config.reloaded"), true); 99 | } 100 | 101 | public void doSaveConfig(ServerCommandSource source) 102 | { 103 | DataProviderManager.INSTANCE.writeToConfig(); 104 | source.sendFeedback(() -> StringUtils.translate("servux.command.config.saved"), true); 105 | } 106 | 107 | public boolean hasDebugMode() 108 | { 109 | return this.debugLog.getValue() || Reference.DEV_DEBUG; 110 | } 111 | 112 | @Override 113 | public boolean hasPermission(ServerPlayerEntity player) 114 | { 115 | if (player == null) 116 | { 117 | return false; 118 | } 119 | 120 | return Permissions.check(player, Reference.MOD_ID+".main.admin", adminPermissionLevel.getValue()); 121 | } 122 | 123 | public boolean hasPermission_EasyPlace(ServerPlayerEntity player) 124 | { 125 | if (player == null) 126 | { 127 | return false; 128 | } 129 | 130 | return Permissions.check(player, Reference.MOD_ID+".main.easy_place", easyPlacePermissionLevel.getValue()); 131 | } 132 | 133 | public boolean isEasyPlaceValidatorEnabled() 134 | { 135 | return this.easyPlaceValidatorEnabled.getValue(); 136 | } 137 | 138 | @Override 139 | public void onTickEndPre() 140 | { 141 | // NO-OP 142 | } 143 | 144 | @Override 145 | public void onTickEndPost() 146 | { 147 | // NO-OP 148 | } 149 | 150 | public String getDefaultLanguage() 151 | { 152 | return defaultLanguage.getValue(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/event/PlayerHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.event; 2 | 3 | import javax.annotation.Nullable; 4 | import java.net.SocketAddress; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.UUID; 8 | import org.jetbrains.annotations.ApiStatus; 9 | import com.mojang.authlib.GameProfile; 10 | import net.minecraft.server.PlayerConfigEntry; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.text.Text; 13 | import fi.dy.masa.servux.interfaces.IPlayerListener; 14 | import fi.dy.masa.servux.interfaces.IPlayerManager; 15 | 16 | public class PlayerHandler implements IPlayerManager 17 | { 18 | private static final PlayerHandler INSTANCE = new PlayerHandler(); 19 | private final List handlers = new ArrayList<>(); 20 | public static IPlayerManager getInstance() { return INSTANCE; } 21 | 22 | @Override 23 | public void registerPlayerHandler(IPlayerListener handler) { 24 | if (!this.handlers.contains(handler)) 25 | { 26 | this.handlers.add(handler); 27 | } 28 | } 29 | 30 | @Override 31 | public void unregisterPlayerHandler(IPlayerListener handler) 32 | { 33 | this.handlers.remove(handler); 34 | } 35 | 36 | @ApiStatus.Internal 37 | public void onClientConnect(SocketAddress addr, PlayerConfigEntry profile, @Nullable Text result) 38 | { 39 | if (!this.handlers.isEmpty()) 40 | { 41 | for (IPlayerListener handler : this.handlers) 42 | { 43 | handler.onClientConnect(addr, profile, result); 44 | } 45 | } 46 | } 47 | 48 | @ApiStatus.Internal 49 | public void onPlayerJoin(SocketAddress addr, GameProfile profile, ServerPlayerEntity player) 50 | { 51 | if (!this.handlers.isEmpty()) 52 | { 53 | for (IPlayerListener handler : this.handlers) 54 | { 55 | handler.onPlayerJoin(addr, profile, player); 56 | } 57 | } 58 | } 59 | 60 | @ApiStatus.Internal 61 | public void onPlayerRespawn(ServerPlayerEntity newPlayer, ServerPlayerEntity oldPlayer) 62 | { 63 | if (!this.handlers.isEmpty()) 64 | { 65 | for (IPlayerListener handler : this.handlers) 66 | { 67 | handler.onPlayerRespawn(newPlayer, oldPlayer); 68 | } 69 | } 70 | } 71 | 72 | @ApiStatus.Internal 73 | public void onPlayerOp(PlayerConfigEntry profile, UUID uuid, @Nullable ServerPlayerEntity player) 74 | { 75 | if (!this.handlers.isEmpty()) 76 | { 77 | for (IPlayerListener handler : this.handlers) 78 | { 79 | handler.onPlayerOp(profile, uuid, player); 80 | } 81 | } 82 | } 83 | 84 | @ApiStatus.Internal 85 | public void onPlayerDeOp(PlayerConfigEntry profile, UUID uuid, @Nullable ServerPlayerEntity player) 86 | { 87 | if (!this.handlers.isEmpty()) 88 | { 89 | for (IPlayerListener handler : this.handlers) 90 | { 91 | handler.onPlayerDeOp(profile, uuid, player); 92 | } 93 | } 94 | } 95 | 96 | @ApiStatus.Internal 97 | public void onPlayerLeave(ServerPlayerEntity player) 98 | { 99 | if (!this.handlers.isEmpty()) 100 | { 101 | for (IPlayerListener handler : this.handlers) 102 | { 103 | handler.onPlayerLeave(player); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/event/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.event; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.jetbrains.annotations.ApiStatus; 6 | 7 | import net.minecraft.resource.ResourceManager; 8 | import net.minecraft.server.MinecraftServer; 9 | 10 | import fi.dy.masa.servux.interfaces.IServerListener; 11 | import fi.dy.masa.servux.interfaces.IServerManager; 12 | 13 | /** 14 | * Interface Handler for Server loading / unloading events --> similar to WorldLoadHandler, 15 | * but it only executes once at the proper time to register packet receivers, etc. 16 | */ 17 | public class ServerHandler implements IServerManager 18 | { 19 | private static final ServerHandler INSTANCE = new ServerHandler(); 20 | private final List handlers = new ArrayList<>(); 21 | public static IServerManager getInstance() { return INSTANCE; } 22 | 23 | @Override 24 | public void registerServerHandler(IServerListener handler) 25 | { 26 | if (!this.handlers.contains(handler)) 27 | { 28 | this.handlers.add(handler); 29 | } 30 | } 31 | 32 | @Override 33 | public void unregisterServerHandler(IServerListener handler) 34 | { 35 | this.handlers.remove(handler); 36 | } 37 | 38 | @ApiStatus.Internal 39 | public void onServerStarting(MinecraftServer server) 40 | { 41 | if (!this.handlers.isEmpty()) 42 | { 43 | for (IServerListener handler : this.handlers) 44 | { 45 | handler.onServerStarting(server); 46 | } 47 | } 48 | } 49 | 50 | @ApiStatus.Internal 51 | public void onServerStarted(MinecraftServer server) 52 | { 53 | if (!this.handlers.isEmpty()) 54 | { 55 | for (IServerListener handler : this.handlers) 56 | { 57 | handler.onServerStarted(server); 58 | } 59 | } 60 | } 61 | 62 | @ApiStatus.Internal 63 | public void onServerResourceReloadPre(MinecraftServer server, ResourceManager resourceManager) 64 | { 65 | if (!this.handlers.isEmpty()) 66 | { 67 | for (IServerListener handler : this.handlers) 68 | { 69 | handler.onServerResourceReloadPre(server, resourceManager); 70 | } 71 | } 72 | } 73 | 74 | @ApiStatus.Internal 75 | public void onServerResourceReloadPost(MinecraftServer server, ResourceManager resourceManager, boolean success) 76 | { 77 | if (!this.handlers.isEmpty()) 78 | { 79 | for (IServerListener handler : this.handlers) 80 | { 81 | handler.onServerResourceReloadPost(server, resourceManager, success); 82 | } 83 | } 84 | } 85 | 86 | @ApiStatus.Internal 87 | public void onServerStopping(MinecraftServer server) 88 | { 89 | if (!this.handlers.isEmpty()) 90 | { 91 | for (IServerListener handler : this.handlers) 92 | { 93 | handler.onServerStopping(server); 94 | } 95 | } 96 | } 97 | 98 | @ApiStatus.Internal 99 | public void onServerStopped(MinecraftServer server) 100 | { 101 | if (!this.handlers.isEmpty()) 102 | { 103 | for (IServerListener handler : this.handlers) 104 | { 105 | handler.onServerStopped(server); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/event/ServerInitHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.event; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.jetbrains.annotations.ApiStatus; 6 | import fi.dy.masa.servux.interfaces.IServerInitDispatcher; 7 | import fi.dy.masa.servux.interfaces.IServerInitHandler; 8 | 9 | public class ServerInitHandler implements IServerInitDispatcher 10 | { 11 | private static final ServerInitHandler INSTANCE = new ServerInitHandler(); 12 | public static IServerInitDispatcher getInstance() { return INSTANCE; } 13 | private final List handlers = new ArrayList<>(); 14 | 15 | @Override 16 | public void registerServerInitHandler(IServerInitHandler handler) 17 | { 18 | if (this.handlers.contains(handler) == false) 19 | { 20 | this.handlers.add(handler); 21 | } 22 | } 23 | 24 | @ApiStatus.Internal 25 | public void onServerInit() 26 | { 27 | if (this.handlers.isEmpty() == false) 28 | { 29 | for (IServerInitHandler handler : this.handlers) 30 | { 31 | handler.onServerInit(); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IPlayerListener.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | import javax.annotation.Nullable; 4 | import java.net.SocketAddress; 5 | import java.util.UUID; 6 | import com.mojang.authlib.GameProfile; 7 | import net.minecraft.server.PlayerConfigEntry; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.text.Text; 10 | 11 | public interface IPlayerListener 12 | { 13 | default void onClientConnect(SocketAddress addr, PlayerConfigEntry profile, Text result) {} 14 | default void onPlayerJoin(SocketAddress addr, GameProfile profile, ServerPlayerEntity player) {} 15 | default void onPlayerRespawn(ServerPlayerEntity newPlayer, ServerPlayerEntity oldPlayer) {} 16 | default void onPlayerOp(PlayerConfigEntry profile, UUID uuid, @Nullable ServerPlayerEntity player) {} 17 | default void onPlayerDeOp(PlayerConfigEntry profile, UUID uuid, @Nullable ServerPlayerEntity player) {} 18 | default void onPlayerLeave(ServerPlayerEntity player) {} 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IPlayerManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | public interface IPlayerManager 4 | { 5 | void registerPlayerHandler(IPlayerListener handler); 6 | void unregisterPlayerHandler(IPlayerListener handler); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IServerCommand.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import net.minecraft.command.CommandRegistryAccess; 5 | import net.minecraft.server.command.CommandManager; 6 | import net.minecraft.server.command.ServerCommandSource; 7 | 8 | public interface IServerCommand 9 | { 10 | /** 11 | * Register a Server Side command 12 | */ 13 | void register(CommandDispatcher dispatcher, 14 | CommandRegistryAccess registryAccess, 15 | CommandManager.RegistrationEnvironment environment); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IServerInitDispatcher.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | public interface IServerInitDispatcher 4 | { 5 | void registerServerInitHandler(IServerInitHandler handler); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IServerInitHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | public interface IServerInitHandler 4 | { 5 | void onServerInit(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IServerListener.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | import net.minecraft.resource.ResourceManager; 4 | import net.minecraft.server.MinecraftServer; 5 | 6 | public interface IServerListener 7 | { 8 | /** 9 | * Called at the initial occurrence of a MinecraftServer is starting up 10 | * @param server (The MinecraftServer object) 11 | */ 12 | default void onServerStarting(MinecraftServer server) {} 13 | 14 | /** 15 | * Called when the local MinecraftServer is finished starting 16 | * @param server (The MinecraftServer object) 17 | */ 18 | default void onServerStarted(MinecraftServer server) {} 19 | 20 | /** 21 | * Called when the Resources (Data Packs) are starting to be reloaded 22 | * @param server (The MinecraftServer object) 23 | * @param resourceManager (The ResourceManager Object) 24 | */ 25 | default void onServerResourceReloadPre(MinecraftServer server, ResourceManager resourceManager) {} 26 | 27 | /** 28 | * Called when the Resources (Data Packs) are finished being reloaded 29 | * @param server (The MinecraftServer object) 30 | */ 31 | default void onServerResourceReloadPost(MinecraftServer server, ResourceManager resourceManager, boolean success) {} 32 | 33 | /** 34 | * Called when the local MinecraftServer enters its initial "stopping" state 35 | * @param server (The MinecraftServer object) 36 | */ 37 | default void onServerStopping(MinecraftServer server) {} 38 | 39 | /** 40 | * Called when the local MinecraftServer finishes it's "stopped" state and before the "server" object itself is killed. 41 | * @param server (The MinecraftServer object) 42 | */ 43 | default void onServerStopped(MinecraftServer server) {} 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/interfaces/IServerManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.interfaces; 2 | 3 | public interface IServerManager 4 | { 5 | void registerServerHandler(IServerListener handler); 6 | void unregisterServerHandler(IServerListener handler); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/DataLogger.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.mojang.serialization.Codec; 5 | import net.minecraft.util.StringIdentifiable; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | public enum DataLogger implements StringIdentifiable 10 | { 11 | TPS ("tps", DataLoggerType.TPS, DataLoggerTPS.CODEC), 12 | MOB_CAPS ("mob_caps", DataLoggerType.MOB_CAPS, DataLoggerMobCaps.CODEC) 13 | ; 14 | 15 | public static final EnumCodec CODEC = StringIdentifiable.createCodec(DataLogger::values); 16 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 17 | 18 | private final String name; 19 | private final DataLoggerType type; 20 | private final Codec codec; 21 | 22 | DataLogger(String name, DataLoggerType type, Codec codec) 23 | { 24 | this.name = name; 25 | this.type = type; 26 | this.codec = codec; 27 | } 28 | 29 | @Override 30 | public String asString() 31 | { 32 | return this.name; 33 | } 34 | 35 | public @Nullable DataLoggerBase init() 36 | { 37 | return this.type.init(this); 38 | } 39 | 40 | public Codec codec() 41 | { 42 | return this.codec; 43 | } 44 | 45 | public static @Nullable DataLogger fromStringStatic(String name) 46 | { 47 | for (DataLogger type : VALUES) 48 | { 49 | if (type.name.equalsIgnoreCase(name)) 50 | { 51 | return type; 52 | } 53 | } 54 | 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/DataLoggerBase.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | 5 | public abstract class DataLoggerBase 6 | { 7 | private final DataLogger type; 8 | 9 | public DataLoggerBase(DataLogger type) 10 | { 11 | this.type = type; 12 | } 13 | 14 | public DataLogger getType() 15 | { 16 | return this.type; 17 | } 18 | 19 | public abstract T getResult(MinecraftServer server); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/DataLoggerMobCaps.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers; 2 | 3 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 4 | 5 | import com.mojang.serialization.Codec; 6 | import net.minecraft.entity.SpawnGroup; 7 | import net.minecraft.nbt.NbtCompound; 8 | import net.minecraft.nbt.NbtOps; 9 | import net.minecraft.server.MinecraftServer; 10 | import net.minecraft.server.world.ServerWorld; 11 | import net.minecraft.world.SpawnHelper; 12 | 13 | import fi.dy.masa.servux.loggers.data.MobCapData; 14 | 15 | public class DataLoggerMobCaps extends DataLoggerBase 16 | { 17 | public static final Codec CODEC = NbtCompound.CODEC; 18 | 19 | public DataLoggerMobCaps(DataLogger type) 20 | { 21 | super(type); 22 | } 23 | 24 | @Override 25 | public NbtCompound getResult(MinecraftServer server) 26 | { 27 | NbtCompound nbt = new NbtCompound(); 28 | 29 | for (ServerWorld world : server.getWorlds()) 30 | { 31 | String dimKey = world.getRegistryKey().getValue().toString(); 32 | MobCapData mobCapData = new MobCapData(); 33 | MobCapData.Cap[] data = MobCapData.createCapArray(); 34 | SpawnHelper.Info info = world.getChunkManager().getSpawnInfo(); 35 | 36 | if (info != null) 37 | { 38 | int spawnableChunks = world.getChunkManager().chunkLoadingManager.getLevelManager().getTickedChunkCount(); 39 | int divisor = 17 * 17; 40 | long worldTime = world.getTime(); 41 | 42 | if (spawnableChunks <= 0) 43 | { 44 | // Servux.debugLog("DataLoggerMobCaps#getResult(): Skipping Dimension: [{}] (No loaded chunks)", dimKey); 45 | continue; // Not loaded 46 | } 47 | 48 | for (Object2IntMap.Entry entry : info.getGroupToCount().object2IntEntrySet()) 49 | { 50 | MobCapData.EntityCategory category = MobCapData.EntityCategory.fromVanillaCategory(entry.getKey()); 51 | 52 | int current = entry.getIntValue(); 53 | int capacity = entry.getKey().getCapacity() * spawnableChunks / divisor; 54 | data[category.ordinal()].setCurrentAndCap(current, capacity); 55 | 56 | for (MobCapData.EntityCategory type : MobCapData.EntityCategory.values()) 57 | { 58 | MobCapData.Cap cap = data[type.ordinal()]; 59 | mobCapData.setCurrentAndCapValues(type, cap.getCurrent(), cap.getCap(), worldTime); 60 | } 61 | } 62 | 63 | try 64 | { 65 | NbtCompound nbtEntry = (NbtCompound) MobCapData.CODEC.encodeStart(world.getRegistryManager().getOps(NbtOps.INSTANCE), mobCapData).getPartialOrThrow(); 66 | nbtEntry.putLong("WorldTick", worldTime); 67 | nbt.put(dimKey, nbtEntry); 68 | } 69 | catch (Exception ignored) { } 70 | 71 | } 72 | } 73 | 74 | return nbt; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/DataLoggerTPS.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import com.mojang.serialization.Codec; 6 | import net.minecraft.nbt.NbtCompound; 7 | import net.minecraft.nbt.NbtOps; 8 | import net.minecraft.server.MinecraftServer; 9 | import net.minecraft.server.ServerTickManager; 10 | 11 | import fi.dy.masa.servux.loggers.data.TPSData; 12 | import fi.dy.masa.servux.mixin.server.IMixinServerTickManager; 13 | 14 | public class DataLoggerTPS extends DataLoggerBase 15 | { 16 | public static final Codec CODEC = NbtCompound.CODEC; 17 | 18 | public DataLoggerTPS(DataLogger type) 19 | { 20 | super(type); 21 | } 22 | 23 | @Override 24 | public NbtCompound getResult(MinecraftServer server) 25 | { 26 | try 27 | { 28 | return (NbtCompound) TPSData.CODEC.encodeStart(server.getRegistryManager().getOps(NbtOps.INSTANCE), this.build(server)).getOrThrow(); 29 | } 30 | catch (Exception e) 31 | { 32 | return new NbtCompound(); 33 | } 34 | } 35 | 36 | private TPSData build(MinecraftServer server) 37 | { 38 | ServerTickManager tickManager = server.getTickManager(); 39 | boolean frozen = tickManager.isFrozen(); 40 | boolean sprinting = tickManager.isSprinting(); 41 | final double mspt = (double) server.getAverageNanosPerTick() / TimeUnit.MILLISECONDS.toNanos(1L); 42 | double tps = 1000.0D / Math.max(sprinting ? 0.0 : tickManager.getMillisPerTick(), mspt); 43 | 44 | if (frozen) 45 | { 46 | tps = 0.0d; 47 | } 48 | 49 | return new TPSData(mspt, 50 | tps, 51 | ((IMixinServerTickManager) tickManager).servux_getStringTicks(), 52 | frozen, 53 | sprinting, 54 | tickManager.isStepping() 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/DataLoggerType.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | public class DataLoggerType> 6 | { 7 | public static final DataLoggerType TPS; 8 | public static final DataLoggerType MOB_CAPS; 9 | 10 | private final DataLoggerFactory factory; 11 | private final DataLogger type; 12 | 13 | private static > DataLoggerType create(DataLoggerFactory factory, DataLogger type) 14 | { 15 | return new DataLoggerType<>(factory, type); 16 | } 17 | 18 | private DataLoggerType(DataLoggerFactory factory, DataLogger type) 19 | { 20 | this.type = type; 21 | this.factory = factory; 22 | } 23 | 24 | @Nullable 25 | public T init(DataLogger fmt) 26 | { 27 | return this.factory.create(fmt); 28 | } 29 | 30 | public DataLogger getType() 31 | { 32 | return this.type; 33 | } 34 | 35 | @FunctionalInterface 36 | interface DataLoggerFactory> 37 | { 38 | T create(DataLogger type); 39 | } 40 | 41 | static 42 | { 43 | TPS = create(DataLoggerTPS::new, DataLogger.TPS); 44 | MOB_CAPS = create(DataLoggerMobCaps::new, DataLogger.MOB_CAPS); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/loggers/data/TPSData.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.loggers.data; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.codecs.PrimitiveCodec; 5 | import com.mojang.serialization.codecs.RecordCodecBuilder; 6 | 7 | public record TPSData(double mspt, double tps, long sprintTicks, boolean frozen, boolean sprinting, boolean stepping) 8 | { 9 | public static Codec CODEC = RecordCodecBuilder.create( 10 | (inst) -> inst.group( 11 | PrimitiveCodec.DOUBLE.fieldOf("mspt").forGetter(TPSData::mspt), 12 | PrimitiveCodec.DOUBLE.fieldOf("tps").forGetter(TPSData::tps), 13 | PrimitiveCodec.LONG.fieldOf("sprintTicks").forGetter(TPSData::sprintTicks), 14 | PrimitiveCodec.BOOL.fieldOf("frozen").forGetter(TPSData::frozen), 15 | PrimitiveCodec.BOOL.fieldOf("sprinting").forGetter(TPSData::sprinting), 16 | PrimitiveCodec.BOOL.fieldOf("stepping").forGetter(TPSData::stepping) 17 | ).apply(inst, TPSData::new)); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/MixinMain.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | 5 | import net.minecraft.registry.DynamicRegistryManager; 6 | import net.minecraft.server.Main; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 11 | 12 | import fi.dy.masa.servux.dataproviders.DataProviderManager; 13 | 14 | @Mixin(Main.class) 15 | public class MixinMain 16 | { 17 | @Inject(method = "main", at = @At(value = "INVOKE", 18 | target = "Lnet/minecraft/world/level/storage/LevelStorage$Session;backupLevelDataFile(Lnet/minecraft/registry/DynamicRegistryManager;Lnet/minecraft/world/SaveProperties;)V", 19 | shift = At.Shift.AFTER)) 20 | private static void servux_onCaptureImmutable(String[] args, CallbackInfo ci, @Local DynamicRegistryManager.Immutable immutable) 21 | { 22 | DataProviderManager.INSTANCE.onCaptureImmutable(immutable); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/block/MixinBlock_UpdateSuppression.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.block; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import net.minecraft.block.Block; 6 | import net.minecraft.entity.ItemEntity; 7 | import net.minecraft.item.ItemStack; 8 | import net.minecraft.world.World; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | import fi.dy.masa.servux.util.WorldUtils; 15 | 16 | @Mixin(Block.class) 17 | public class MixinBlock_UpdateSuppression 18 | { 19 | @Inject(method = "dropStack(Lnet/minecraft/world/World;Ljava/util/function/Supplier;Lnet/minecraft/item/ItemStack;)V", 20 | at = @At("HEAD"), cancellable = true) 21 | private static void servux_preventItemDrops(World world, 22 | Supplier itemEntitySupplier, 23 | ItemStack stack, 24 | CallbackInfo ci) 25 | { 26 | if (WorldUtils.shouldPreventBlockUpdates(world)) 27 | { 28 | ci.cancel(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/block/MixinChestBlock.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.block; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.block.ChestBlock; 5 | import net.minecraft.block.enums.ChestType; 6 | import net.minecraft.util.BlockMirror; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Inject; 10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 11 | 12 | import fi.dy.masa.servux.dataproviders.LitematicsDataProvider; 13 | import fi.dy.masa.servux.util.BlockUtils; 14 | 15 | @Mixin(ChestBlock.class) 16 | public class MixinChestBlock 17 | { 18 | @Inject(method = "mirror", at = @At("HEAD"), cancellable = true) 19 | private void servux_fixChestMirror(BlockState state, BlockMirror mirror, CallbackInfoReturnable cir) 20 | { 21 | ChestType type = state.get(ChestBlock.CHEST_TYPE); 22 | 23 | if (LitematicsDataProvider.INSTANCE.isEnabled() && 24 | LitematicsDataProvider.INSTANCE.fixChestMirror.getValue() 25 | && type != ChestType.SINGLE) 26 | { 27 | state = BlockUtils.fixMirrorDoubleChest(state, mirror, type); 28 | cir.setReturnValue(state); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/block/MixinHopperBlockEntity.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.block; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import fi.dy.masa.servux.dataproviders.TweaksDataProvider; 6 | import fi.dy.masa.servux.util.InventoryUtils; 7 | import net.minecraft.block.entity.HopperBlockEntity; 8 | import net.minecraft.item.ItemStack; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | /** 15 | * ... by KikuGie 16 | * Priority 999 if installed with stackable-shulkers-fix 17 | */ 18 | @Mixin(value = HopperBlockEntity.class, priority = 999) 19 | public class MixinHopperBlockEntity 20 | { 21 | @WrapOperation( 22 | method = "isFull", 23 | at = @At(value = "INVOKE", 24 | target = "Lnet/minecraft/item/ItemStack;getMaxCount()I") 25 | ) 26 | private int servux_modifyShulkerMaxCount(ItemStack instance, Operation original) 27 | { 28 | if (TweaksDataProvider.INSTANCE.isStackableShulkersFixActive()) 29 | { 30 | return InventoryUtils.isShulkerBox(instance) ? instance.getCount() : original.call(instance); 31 | } 32 | 33 | return original.call(instance); 34 | } 35 | 36 | @WrapOperation( 37 | method = "isInventoryFull", 38 | at = @At(value = "INVOKE", 39 | target = "Lnet/minecraft/item/ItemStack;getMaxCount()I") 40 | ) 41 | private static int servux_modifyShulkerMaxCountStatic(ItemStack instance, Operation original) 42 | { 43 | if (TweaksDataProvider.INSTANCE.isStackableShulkersFixActive()) 44 | { 45 | return InventoryUtils.isShulkerBox(instance) ? 1 : original.call(instance); 46 | } 47 | 48 | return original.call(instance); 49 | } 50 | 51 | @Inject( 52 | method = "canMergeItems", 53 | at = @At("HEAD"), 54 | cancellable = true 55 | ) 56 | private static void servux_cancelItemMerging(ItemStack first, ItemStack second, CallbackInfoReturnable cir) 57 | { 58 | if (TweaksDataProvider.INSTANCE.isStackableShulkersFixActive()) 59 | { 60 | if (InventoryUtils.isShulkerBox(first) || InventoryUtils.isShulkerBox(second)) cir.setReturnValue(false); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/block/MixinRailBlocks.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.block; 2 | 3 | import net.minecraft.block.*; 4 | import net.minecraft.block.enums.RailShape; 5 | import net.minecraft.util.BlockRotation; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | 11 | import fi.dy.masa.servux.dataproviders.LitematicsDataProvider; 12 | 13 | @Mixin({ RailBlock.class, DetectorRailBlock.class, PoweredRailBlock.class}) 14 | public abstract class MixinRailBlocks extends AbstractRailBlock 15 | { 16 | protected MixinRailBlocks(boolean disableCorners, Settings builder) 17 | { 18 | super(disableCorners, builder); 19 | } 20 | 21 | @Inject(method = "rotate", at = @At("HEAD"), cancellable = true) 22 | private void servux_fixRailRotation(BlockState state, BlockRotation rot, CallbackInfoReturnable cir) 23 | { 24 | if (LitematicsDataProvider.INSTANCE.isEnabled() && 25 | LitematicsDataProvider.INSTANCE.fixRaiLRotations.getValue() && 26 | rot == BlockRotation.CLOCKWISE_180) 27 | { 28 | RailShape shape = null; 29 | 30 | if (((Object) this) instanceof RailBlock) 31 | { 32 | shape = state.get(RailBlock.SHAPE); 33 | } 34 | else if (((Object) this) instanceof DetectorRailBlock) 35 | { 36 | shape = state.get(DetectorRailBlock.SHAPE); 37 | } 38 | else if (((Object) this) instanceof PoweredRailBlock) 39 | { 40 | shape = state.get(PoweredRailBlock.SHAPE); 41 | } 42 | 43 | // Fix the incomplete switch statement causing the ccw_90 rotation being used instead 44 | // for the 180 degree rotation of the straight rails. 45 | if (shape == RailShape.EAST_WEST || shape == RailShape.NORTH_SOUTH) 46 | { 47 | cir.setReturnValue(state); 48 | cir.cancel(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/block/MixinStairsBlock.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.block; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.block.StairsBlock; 6 | import net.minecraft.block.enums.StairShape; 7 | import net.minecraft.util.BlockMirror; 8 | import net.minecraft.util.BlockRotation; 9 | import net.minecraft.util.math.Direction; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | import fi.dy.masa.servux.dataproviders.LitematicsDataProvider; 16 | 17 | import static net.minecraft.block.StairsBlock.FACING; 18 | import static net.minecraft.block.StairsBlock.SHAPE; 19 | 20 | @Mixin(StairsBlock.class) 21 | public abstract class MixinStairsBlock extends Block 22 | { 23 | public MixinStairsBlock(Settings settings) 24 | { 25 | super(settings); 26 | } 27 | 28 | @Inject(method = "mirror", at = @At(value = "HEAD"), cancellable = true) 29 | private void servux_fixStairsMirror(BlockState state, BlockMirror mirror, CallbackInfoReturnable cir) 30 | { 31 | if (LitematicsDataProvider.INSTANCE.isEnabled() && 32 | LitematicsDataProvider.INSTANCE.fixStairMirror.getValue()) 33 | { 34 | Direction direction = state.get(FACING); 35 | StairShape stairShape = state.get(SHAPE); 36 | 37 | // Fixes X Axis for FRONT_BACK being inverted for INNER_LEFT / INNER_RIGHT 38 | if (direction.getAxis() == Direction.Axis.X && mirror == BlockMirror.FRONT_BACK) 39 | { 40 | cir.setReturnValue( 41 | switch (stairShape) 42 | { 43 | case INNER_LEFT -> state.rotate(BlockRotation.CLOCKWISE_180).with(SHAPE, StairShape.INNER_RIGHT); 44 | case INNER_RIGHT -> state.rotate(BlockRotation.CLOCKWISE_180).with(SHAPE, StairShape.INNER_LEFT); 45 | case OUTER_LEFT -> state.rotate(BlockRotation.CLOCKWISE_180).with(SHAPE, StairShape.OUTER_RIGHT); 46 | case OUTER_RIGHT -> state.rotate(BlockRotation.CLOCKWISE_180).with(SHAPE, StairShape.OUTER_LEFT); 47 | default -> state.rotate(BlockRotation.CLOCKWISE_180); 48 | } 49 | ); 50 | 51 | cir.cancel(); 52 | } 53 | // Fixes missing Axis STAIR_SHAPE flips 54 | else if ((direction.getAxis() == Direction.Axis.X && mirror == BlockMirror.LEFT_RIGHT) || 55 | (direction.getAxis() == Direction.Axis.Z && mirror == BlockMirror.FRONT_BACK)) 56 | { 57 | cir.setReturnValue( 58 | switch (stairShape) 59 | { 60 | case INNER_LEFT -> state.with(SHAPE, StairShape.INNER_RIGHT); 61 | case INNER_RIGHT -> state.with(SHAPE, StairShape.INNER_LEFT); 62 | case OUTER_LEFT -> state.with(SHAPE, StairShape.OUTER_RIGHT); 63 | case OUTER_RIGHT -> state.with(SHAPE, StairShape.OUTER_LEFT); 64 | default -> state; 65 | } 66 | ); 67 | 68 | cir.cancel(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/debug/IMixinMobEntity.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.debug; 2 | 3 | import net.minecraft.entity.ai.goal.GoalSelector; 4 | import net.minecraft.entity.mob.MobEntity; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(MobEntity.class) 9 | public interface IMixinMobEntity 10 | { 11 | @Accessor("goalSelector") 12 | GoalSelector servux_getGoalSelector(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/debug/MixinDebugInfoSender.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.debug; 2 | 3 | //@Mixin(value = DebugInfoSender.class) 4 | @Deprecated(forRemoval = true) 5 | public class MixinDebugInfoSender 6 | { 7 | // @Shadow 8 | // private static List listMemories(LivingEntity entity, long currentTime) 9 | // { 10 | // throw new AssertionError(); 11 | // } 12 | // 13 | // @Inject(method = "addGameTestMarker", at = @At("HEAD")) 14 | // private static void servux_onAddGameTestMarker(ServerWorld world, BlockPos pos, String message, int color, int duration, CallbackInfo ci) 15 | // { 16 | // // NO-OP 17 | // } 18 | // 19 | // @Inject(method = "clearGameTestMarkers", at = @At("HEAD")) 20 | // private static void servux_onClearGameTestMarkers(ServerWorld world, CallbackInfo ci) 21 | // { 22 | // // NO-OP 23 | // } 24 | // 25 | // @Inject(method = "sendChunkWatchingChange", at = @At("HEAD")) 26 | // private static void servux_onChunkWatchingChange(ServerWorld world, ChunkPos pos, CallbackInfo ci) 27 | // { 28 | // DebugDataProvider.INSTANCE.sendChunkWatchingChange(world, pos); 29 | // } 30 | // 31 | // @Inject(method = "sendPoiAddition", at = @At("HEAD")) 32 | // private static void servux_onSendPoiAddition(ServerWorld world, BlockPos pos, CallbackInfo ci) 33 | // { 34 | // DebugDataProvider.INSTANCE.sendPoiAdditions(world, pos); 35 | // } 36 | // 37 | // @Inject(method = "sendPoiRemoval", at = @At("HEAD")) 38 | // private static void servux_onSendPoiRemoval(ServerWorld world, BlockPos pos, CallbackInfo ci) 39 | // { 40 | // DebugDataProvider.INSTANCE.sendPoiRemoval(world, pos); 41 | // } 42 | // 43 | // @Inject(method = "sendPointOfInterest", at = @At("HEAD")) 44 | // private static void servux_onSendPointOfInterest(ServerWorld world, BlockPos pos, CallbackInfo ci) 45 | // { 46 | // DebugDataProvider.INSTANCE.sendPointOfInterest(world, pos); 47 | // } 48 | // 49 | // @Inject(method = "sendPoi", at = @At("HEAD")) 50 | // private static void servux_onSendPoi(ServerWorld world, BlockPos pos, CallbackInfo ci) 51 | // { 52 | // DebugDataProvider.INSTANCE.sendPoi(world, pos); 53 | // } 54 | // 55 | // //FIXME (CustomPayload Error) 56 | // @Inject(method = "sendPathfindingData", at = @At("HEAD")) 57 | // private static void servux_onSendPathfindingData(World world, MobEntity mob, @Nullable Path path, float nodeReachProximity, CallbackInfo ci) 58 | // { 59 | // if (world instanceof ServerWorld serverWorld) 60 | // { 61 | // DebugDataProvider.INSTANCE.sendPathfindingData(serverWorld, mob, path, nodeReachProximity); 62 | // } 63 | // } 64 | // 65 | // @Inject(method = "sendNeighborUpdate", at = @At("HEAD")) 66 | // private static void servux_onSendNeighborUpdate(World world, BlockPos pos, CallbackInfo ci) 67 | // { 68 | // if (world instanceof ServerWorld serverWorld) 69 | // { 70 | // DebugDataProvider.INSTANCE.sendNeighborUpdate(serverWorld, pos); 71 | // } 72 | // } 73 | // 74 | // @Inject(method = "sendRedstoneUpdateOrder", at = @At("HEAD")) 75 | // private static void servux_onSendRedstoneUpdateOrder(World world, DebugRedstoneUpdateOrderCustomPayload payload, CallbackInfo ci) 76 | // { 77 | // // NO-OP 78 | // } 79 | // 80 | // @Inject(method = "sendStructureStart", at = @At("HEAD")) 81 | // private static void servux_onSendStructureStart(StructureWorldAccess world, StructureStart structureStart, CallbackInfo ci) 82 | // { 83 | // DebugDataProvider.INSTANCE.sendStructureStart(world, structureStart); 84 | // } 85 | // 86 | // @Inject(method = "sendGoalSelector", at = @At("HEAD")) 87 | // private static void servux_onSendGoalSelector(World world, MobEntity mob, GoalSelector goalSelector, CallbackInfo ci) 88 | // { 89 | // if (world instanceof ServerWorld serverWorld) 90 | // { 91 | // DebugDataProvider.INSTANCE.sendGoalSelector(serverWorld, mob, goalSelector); 92 | // } 93 | // } 94 | // 95 | // @Inject(method = "sendRaids", at = @At("HEAD")) 96 | // private static void servux_onSendRaids(ServerWorld server, Collection raids, CallbackInfo ci) 97 | // { 98 | // DebugDataProvider.INSTANCE.sendRaids(server, raids); 99 | // } 100 | // 101 | // //FIXME (CustomPayload Error) 102 | // @Inject(method = "sendBrainDebugData", at = @At("HEAD")) 103 | // private static void servux_onSendBrainDebugData(LivingEntity living, CallbackInfo ci) 104 | // { 105 | // if (living.getEntityWorld() instanceof ServerWorld world) 106 | // { 107 | // DebugDataProvider.INSTANCE.sendBrainDebugData(world, living); 108 | // } 109 | // } 110 | // 111 | // //FIXME (CustomPayload Error) 112 | // @Inject(method = "sendBeeDebugData", at = @At("HEAD")) 113 | // private static void servux_onSendBeeDebugData(BeeEntity bee, CallbackInfo ci) 114 | // { 115 | // if (bee.getEntityWorld() instanceof ServerWorld world) 116 | // { 117 | // DebugDataProvider.INSTANCE.sendBeeDebugData(world, bee); 118 | // } 119 | // } 120 | // 121 | // @Inject(method = "sendBreezeDebugData", at = @At("HEAD")) 122 | // private static void servux_onSendBreezeDebugData(BreezeEntity breeze, CallbackInfo ci) 123 | // { 124 | // if (breeze.getEntityWorld() instanceof ServerWorld world) 125 | // { 126 | // DebugDataProvider.INSTANCE.sendBreezeDebugData(world, breeze); 127 | // } 128 | // } 129 | // 130 | // @Inject(method = "sendGameEvent", at = @At("HEAD")) 131 | // private static void servux_onSendGameEvent(World world, RegistryEntry event, Vec3d pos, CallbackInfo ci) 132 | // { 133 | // if (world instanceof ServerWorld serverWorld) 134 | // { 135 | // DebugDataProvider.INSTANCE.sendGameEvent(serverWorld, event, pos); 136 | // } 137 | // } 138 | // 139 | // @Inject(method = "sendGameEventListener", at = @At("HEAD")) 140 | // private static void servux_onSendGameEventListener(World world, GameEventListener eventListener, CallbackInfo ci) 141 | // { 142 | // if (world instanceof ServerWorld serverWorld) 143 | // { 144 | // DebugDataProvider.INSTANCE.sendGameEventListener(serverWorld, eventListener); 145 | // } 146 | // } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/debug/MixinPath.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.debug; 2 | 3 | import net.minecraft.entity.ai.pathing.Path; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | 6 | @Mixin(Path.class) 7 | public class MixinPath 8 | { 9 | // @Shadow @Final private List nodes; 10 | // @Shadow @Nullable private Path.DebugNodeInfo debugNodeInfos; 11 | // @Shadow @Final private BlockPos target; 12 | // 13 | // @Inject(method = "toBuf", at = @At("HEAD")) 14 | // private void servux_PathfindingFix(PacketByteBuf buf, CallbackInfo ci) 15 | // { 16 | // this.debugNodeInfos = new Path.DebugNodeInfo(this.nodes.stream().filter((pathNode) -> 17 | // !pathNode.visited).toArray(PathNode[]::new), this.nodes.stream().filter((pathNode) -> 18 | // pathNode.visited).toArray(PathNode[]::new), Set.of(new TargetPathNode(this.target.getX(), this.target.getY(), this.target.getZ()))); 19 | // } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/debug/MixinSharedConstants.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.debug; 2 | 3 | import net.minecraft.SharedConstants; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Mutable; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | 8 | @Mixin(SharedConstants.class) 9 | public abstract class MixinSharedConstants 10 | { 11 | @Shadow @Mutable public static boolean isDevelopment; 12 | 13 | public MixinSharedConstants() {} 14 | 15 | /* 16 | @Inject(method = "", at = @At("TAIL")) 17 | private static void servux_enableServerDevelopmentMode(CallbackInfo ci) 18 | { 19 | if (DebugDataProvider.INSTANCE.isEnabled() && 20 | DebugDataProvider.INSTANCE.isServerDevelopmentMode()) 21 | { 22 | isDevelopment = true; 23 | } 24 | } 25 | */ 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/entity/MixinAllayEntity.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.entity; 2 | 3 | import net.minecraft.entity.ai.brain.Brain; 4 | import net.minecraft.entity.passive.AllayEntity; 5 | import net.minecraft.world.GameRules; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | import fi.dy.masa.servux.dataproviders.EntitiesDataProvider; 12 | 13 | @Mixin(AllayEntity.class) 14 | public abstract class MixinAllayEntity 15 | { 16 | // @Shadow public abstract Brain getBrain(); 17 | 18 | @Redirect(method = "canGather", 19 | at = @At(value = "INVOKE", 20 | target = "Lnet/minecraft/world/GameRules;getBoolean(Lnet/minecraft/world/GameRules$Key;)Z")) 21 | private boolean servux$fixAllayGathering1(GameRules instance, GameRules.Key rule) 22 | { 23 | if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering()) 24 | { 25 | return true; 26 | } 27 | 28 | return instance.getBoolean(rule); 29 | } 30 | 31 | // @Inject(method = "isItemPickupCoolingDown", 32 | // at = @At("RETURN"), cancellable = true) 33 | // private void servux$fixAllayGathering2(CallbackInfoReturnable cir) 34 | // { 35 | // if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering() && 36 | // cir.getReturnValue()) 37 | // { 38 | // cir.setReturnValue(false); 39 | // } 40 | // } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/entity/MixinItemEntity.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.entity; 2 | 3 | import net.minecraft.entity.ItemEntity; 4 | import net.minecraft.entity.damage.DamageSource; 5 | import net.minecraft.entity.passive.AllayEntity; 6 | import net.minecraft.server.world.ServerWorld; 7 | import net.minecraft.world.GameRules; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.Redirect; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | import fi.dy.masa.servux.dataproviders.EntitiesDataProvider; 16 | 17 | @Mixin(ItemEntity.class) 18 | public class MixinItemEntity 19 | { 20 | @Unique private boolean isAllay = false; 21 | 22 | @Inject(method = "damage", at = @At("HEAD")) 23 | private void servux$fixAllayGathering5(ServerWorld world, DamageSource source, float amount, CallbackInfoReturnable cir) 24 | { 25 | if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering() && 26 | source.getAttacker() instanceof AllayEntity) 27 | { 28 | this.isAllay = true; 29 | } 30 | } 31 | 32 | @Redirect(method = "damage", 33 | at = @At(value = "INVOKE", 34 | target = "Lnet/minecraft/world/GameRules;getBoolean(Lnet/minecraft/world/GameRules$Key;)Z")) 35 | private boolean servux$fixAllayGathering6(GameRules instance, GameRules.Key rule) 36 | { 37 | if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering() && 38 | this.isAllay) 39 | { 40 | return true; 41 | } 42 | 43 | this.isAllay = false; 44 | return instance.getBoolean(rule); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/entity/MixinMobEntity.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.entity; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.entity.EntityType; 5 | import net.minecraft.entity.mob.MobEntity; 6 | import net.minecraft.world.GameRules; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | import fi.dy.masa.servux.dataproviders.EntitiesDataProvider; 15 | 16 | @Mixin(MobEntity.class) 17 | public abstract class MixinMobEntity 18 | { 19 | @Unique boolean isAllay = false; 20 | 21 | @Inject(method = "tickMovement", at = @At("HEAD")) 22 | private void servux$fixAllayGathering3(CallbackInfo ci) 23 | { 24 | if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering()) 25 | { 26 | Entity entity = (Entity) (Object) this; 27 | 28 | if (entity.getType() == EntityType.ALLAY) 29 | { 30 | this.isAllay = true; 31 | } 32 | } 33 | } 34 | 35 | @Redirect(method = "tickMovement", 36 | at = @At(value = "INVOKE", 37 | target = "Lnet/minecraft/world/GameRules;getBoolean(Lnet/minecraft/world/GameRules$Key;)Z")) 38 | private boolean servux$fixAllayGathering4(GameRules instance, GameRules.Key rule) 39 | { 40 | if (EntitiesDataProvider.INSTANCE.hasFixAllayGathering() && 41 | this.isAllay) 42 | { 43 | return true; 44 | } 45 | 46 | this.isAllay = false; 47 | return instance.getBoolean(rule); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/item/MixinBlockItem_EasyPlace.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.item; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.Shadow; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 8 | import net.minecraft.block.Block; 9 | import net.minecraft.block.BlockState; 10 | import net.minecraft.item.BlockItem; 11 | import net.minecraft.item.Item; 12 | import net.minecraft.item.ItemPlacementContext; 13 | import net.minecraft.server.network.ServerPlayerEntity; 14 | import fi.dy.masa.servux.dataproviders.ServuxConfigProvider; 15 | import fi.dy.masa.servux.util.PlacementHandler; 16 | import fi.dy.masa.servux.util.PlacementHandler.UseContext; 17 | 18 | /** 19 | * Should override Carpet-Extra's version with a higher priority 20 | */ 21 | @Mixin(value = BlockItem.class, priority = 1010) 22 | public abstract class MixinBlockItem_EasyPlace extends Item 23 | { 24 | private MixinBlockItem_EasyPlace(Settings builder) 25 | { 26 | super(builder); 27 | } 28 | 29 | @Shadow protected abstract boolean canPlace(ItemPlacementContext context, BlockState state); 30 | @Shadow public abstract Block getBlock(); 31 | 32 | @Inject(method = "getPlacementState", at = @At("HEAD"), cancellable = true) 33 | private void servux_modifyPlacementState(ItemPlacementContext ctx, CallbackInfoReturnable cir) 34 | { 35 | if (ctx.getPlayer() instanceof ServerPlayerEntity player) 36 | { 37 | if (ServuxConfigProvider.INSTANCE.hasPermission_EasyPlace(player) == false) 38 | { 39 | return; 40 | } 41 | } 42 | 43 | BlockState stateOrig = this.getBlock().getPlacementState(ctx); 44 | 45 | if (stateOrig != null) 46 | { 47 | 48 | if (!ServuxConfigProvider.INSTANCE.isEasyPlaceValidatorEnabled() || this.canPlace(ctx, stateOrig)) 49 | { 50 | UseContext context = UseContext.from(ctx, ctx.getHand()); 51 | cir.setReturnValue(PlacementHandler.applyPlacementProtocolV3(stateOrig, context)); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/item/MixinItemStack.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.item; 2 | 3 | import fi.dy.masa.servux.dataproviders.TweaksDataProvider; 4 | import fi.dy.masa.servux.util.InventoryUtils; 5 | import net.minecraft.component.ComponentMap; 6 | import net.minecraft.component.DataComponentTypes; 7 | import net.minecraft.item.ItemStack; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(ItemStack.class) 15 | public abstract class MixinItemStack 16 | { 17 | @Shadow public abstract ComponentMap getComponents(); 18 | 19 | @Inject(method = "getMaxCount", at = @At("RETURN"), cancellable = true) 20 | public void servux_getMaxStackSizeStackSensitive(CallbackInfoReturnable cir) 21 | { 22 | if (TweaksDataProvider.INSTANCE.shouldEmptyShulkersStack() && 23 | InventoryUtils.isShulkerBox((ItemStack) (Object) this) && 24 | InventoryUtils.shulkerBoxHasItems((ItemStack) (Object) this) == false) 25 | { 26 | final int result = TweaksDataProvider.INSTANCE.getEmptyShulkersMaxCount((ItemStack) (Object) this); 27 | 28 | if (this.getComponents().getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 1) < result) 29 | { 30 | cir.setReturnValue(result); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/nbt/IMixinNbtReadView.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.nbt; 2 | 3 | import net.minecraft.nbt.NbtCompound; 4 | import net.minecraft.storage.NbtReadView; 5 | import net.minecraft.storage.ReadContext; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | @Mixin(NbtReadView.class) 10 | public interface IMixinNbtReadView 11 | { 12 | @Accessor("context") 13 | ReadContext servux_getContext(); 14 | 15 | @Accessor("nbt") 16 | NbtCompound servux_getNbt(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/nbt/IMixinNbtWriteView.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.nbt; 2 | 3 | import com.mojang.serialization.DynamicOps; 4 | import net.minecraft.nbt.NbtCompound; 5 | import net.minecraft.storage.NbtWriteView; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | @Mixin(NbtWriteView.class) 10 | public interface IMixinNbtWriteView 11 | { 12 | @Accessor("ops") 13 | DynamicOps servux_getOps(); 14 | 15 | @Accessor("nbt") 16 | NbtCompound servux_getNbt(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/network/MixinServerPlayNetworkHandler_EasyPlace.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.network; 2 | 3 | import net.minecraft.server.network.ServerPlayNetworkHandler; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.Vec3d; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(value = ServerPlayNetworkHandler.class, priority = 1010) 12 | public class MixinServerPlayNetworkHandler_EasyPlace 13 | { 14 | @Shadow public ServerPlayerEntity player; 15 | 16 | @Redirect(method = "onPlayerInteractBlock", require = 0, 17 | at = @At(value = "INVOKE", 18 | target = "Lnet/minecraft/util/math/Vec3d;subtract(Lnet/minecraft/util/math/Vec3d;)Lnet/minecraft/util/math/Vec3d;")) 19 | private Vec3d servux$removeHitPosCheck(Vec3d hitVec, Vec3d blockCenter) 20 | { 21 | return Vec3d.ZERO; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/network/MixinServerPlayNetworkHandler_QueryNbt.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.network; 2 | 3 | import net.minecraft.server.network.ServerPlayNetworkHandler; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | import org.spongepowered.asm.mixin.injection.Constant; 8 | import org.spongepowered.asm.mixin.injection.ModifyConstant; 9 | 10 | import fi.dy.masa.servux.dataproviders.EntitiesDataProvider; 11 | 12 | @Mixin(value = ServerPlayNetworkHandler.class, priority = 1005) 13 | public class MixinServerPlayNetworkHandler_QueryNbt 14 | { 15 | @Shadow public ServerPlayerEntity player; 16 | 17 | @ModifyConstant(method = "onQueryBlockNbt", constant = @Constant(intValue = 2)) 18 | private int servux_onQueryBlockNbt(int constant) 19 | { 20 | if (EntitiesDataProvider.INSTANCE.hasNbtQueryOverride()) 21 | { 22 | if (EntitiesDataProvider.INSTANCE.hasNbtQueryPermission(this.player)) 23 | { 24 | //Servux.debugLog("received NbtQueryBlock request from: {}", this.player.getName().getLiteralString()); 25 | return 0; 26 | } 27 | else 28 | { 29 | return 4; 30 | } 31 | } 32 | else 33 | { 34 | return constant; 35 | } 36 | } 37 | 38 | @ModifyConstant(method = "onQueryEntityNbt", constant = @Constant(intValue = 2)) 39 | private int servux_onQueryEntityNbt(int constant) 40 | { 41 | if (EntitiesDataProvider.INSTANCE.hasNbtQueryOverride()) 42 | { 43 | if (EntitiesDataProvider.INSTANCE.hasNbtQueryPermission(this.player)) 44 | { 45 | //Servux.debugLog("received NbtQueryEntity request from: {}", this.player.getName().getLiteralString()); 46 | return 0; 47 | } 48 | else 49 | { 50 | return 4; 51 | } 52 | } 53 | else 54 | { 55 | return constant; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/server/IMixinServerTickManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.server; 2 | 3 | import net.minecraft.server.ServerTickManager; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(ServerTickManager.class) 8 | public interface IMixinServerTickManager 9 | { 10 | @Accessor("sprintTicks") 11 | long servux_getStringTicks(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/server/MixinCommandManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.server; 2 | 3 | import org.spongepowered.asm.mixin.Final; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.Shadow; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | import com.mojang.brigadier.CommandDispatcher; 10 | import net.minecraft.command.CommandRegistryAccess; 11 | import net.minecraft.server.command.CommandManager; 12 | import net.minecraft.server.command.ServerCommandSource; 13 | import fi.dy.masa.servux.commands.CommandProvider; 14 | 15 | @Mixin(CommandManager.class) 16 | public class MixinCommandManager 17 | { 18 | @Shadow @Final private CommandDispatcher dispatcher; 19 | 20 | @Inject(method = "", at = @At(value = "INVOKE", 21 | target = "Lnet/minecraft/server/dedicated/command/WhitelistCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V", 22 | shift = At.Shift.AFTER)) 23 | private void servux_injectCommands(CommandManager.RegistrationEnvironment environment, 24 | CommandRegistryAccess registryAccess, CallbackInfo ci) 25 | { 26 | ((CommandProvider) CommandProvider.getInstance()).registerCommands(this.dispatcher, registryAccess, environment); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/server/MixinMinecraftDedicatedServer.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.server; 2 | 3 | import com.mojang.datafixers.DataFixer; 4 | import fi.dy.masa.servux.event.ServerInitHandler; 5 | import net.minecraft.resource.ResourcePackManager; 6 | import net.minecraft.server.SaveLoader; 7 | import net.minecraft.server.dedicated.MinecraftDedicatedServer; 8 | import net.minecraft.server.dedicated.ServerPropertiesLoader; 9 | import net.minecraft.util.ApiServices; 10 | import net.minecraft.world.level.storage.LevelStorage; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(MinecraftDedicatedServer.class) 17 | public class MixinMinecraftDedicatedServer 18 | { 19 | @Inject(method = "", at = @At("TAIL")) 20 | private void servux_DedicatedServerInit(Thread serverThread, LevelStorage.Session session, 21 | ResourcePackManager dataPackManager, SaveLoader saveLoader, 22 | ServerPropertiesLoader propertiesLoader, DataFixer dataFixer, 23 | ApiServices apiServices, CallbackInfo ci) 24 | { 25 | ((ServerInitHandler) ServerInitHandler.getInstance()).onServerInit(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/server/MixinMinecraftServer.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.server; 2 | 3 | import java.util.Collection; 4 | import java.util.concurrent.CompletableFuture; 5 | import java.util.function.BooleanSupplier; 6 | import com.llamalad7.mixinextras.sugar.Local; 7 | 8 | import net.minecraft.resource.ResourceManager; 9 | import net.minecraft.server.MinecraftServer; 10 | import net.minecraft.util.math.GlobalPos; 11 | import net.minecraft.util.profiler.Profiler; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 18 | 19 | import fi.dy.masa.servux.dataproviders.DataProviderManager; 20 | import fi.dy.masa.servux.dataproviders.HudDataProvider; 21 | import fi.dy.masa.servux.event.ServerHandler; 22 | 23 | @Mixin(MinecraftServer.class) 24 | public abstract class MixinMinecraftServer 25 | { 26 | @Shadow private int ticks; 27 | @Shadow public abstract ResourceManager getResourceManager(); 28 | 29 | @Shadow 30 | public abstract GlobalPos getSpawnPos(); 31 | 32 | @Inject(method = "tick", at = @At(value = "RETURN", ordinal = 1)) 33 | private void servux_onTickEnd(BooleanSupplier supplier, CallbackInfo ci, @Local Profiler profiler) 34 | { 35 | profiler.push("servux_tick"); 36 | DataProviderManager.INSTANCE.tickProviders((MinecraftServer) (Object) this, this.ticks, profiler); 37 | profiler.pop(); 38 | } 39 | 40 | @Inject(method = "prepareStartRegion", 41 | at = @At(value = "INVOKE", 42 | target = "Lnet/minecraft/server/MinecraftServer;updateMobSpawnOptions()V", 43 | shift = At.Shift.BEFORE) 44 | ) 45 | private void servux_onPrepareStartRegion(CallbackInfo ci) 46 | { 47 | if (HudDataProvider.INSTANCE.isEnabled()) 48 | { 49 | HudDataProvider.INSTANCE.setSpawnPos(this.getSpawnPos()); 50 | // HudDataProvider.INSTANCE.setSpawnChunkRadius(i); 51 | } 52 | } 53 | 54 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;setupServer()Z"), method = "runServer") 55 | private void servux_onServerStarting(CallbackInfo ci) 56 | { 57 | ((ServerHandler) ServerHandler.getInstance()).onServerStarting((MinecraftServer) (Object) this); 58 | } 59 | 60 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;createMetadata()Lnet/minecraft/server/ServerMetadata;", ordinal = 0), method = "runServer") 61 | private void servux_onServerStarted(CallbackInfo ci) 62 | { 63 | ((ServerHandler) ServerHandler.getInstance()).onServerStarted((MinecraftServer) (Object) this); 64 | } 65 | 66 | @Inject(method = "reloadResources", at = @At("HEAD")) 67 | private void servux_startResourceReload(Collection collection, CallbackInfoReturnable> cir) 68 | { 69 | ((ServerHandler) ServerHandler.getInstance()).onServerResourceReloadPre((MinecraftServer) (Object) this, this.getResourceManager()); 70 | } 71 | 72 | @Inject(method = "reloadResources", at = @At("TAIL")) 73 | private void servux_endResourceReload(Collection collection, CallbackInfoReturnable> cir) 74 | { 75 | cir.getReturnValue().handleAsync((value, throwable) -> 76 | { 77 | ((ServerHandler) ServerHandler.getInstance()).onServerResourceReloadPost((MinecraftServer) (Object) this, this.getResourceManager(), throwable == null); 78 | return value; 79 | }, (MinecraftServer) (Object) this); 80 | } 81 | 82 | @Inject(at = @At("HEAD"), method = "shutdown") 83 | private void servux_onServerStopping(CallbackInfo info) 84 | { 85 | ((ServerHandler) ServerHandler.getInstance()).onServerStopping((MinecraftServer) (Object) this); 86 | } 87 | 88 | @Inject(at = @At("TAIL"), method = "shutdown") 89 | private void servux_onServerStopped(CallbackInfo info) 90 | { 91 | ((ServerHandler) ServerHandler.getInstance()).onServerStopped((MinecraftServer) (Object) this); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/server/MixinPlayerManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.server; 2 | 3 | import java.net.SocketAddress; 4 | import java.util.Optional; 5 | import java.util.UUID; 6 | 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.network.ClientConnection; 9 | import net.minecraft.server.PlayerConfigEntry; 10 | import net.minecraft.server.PlayerManager; 11 | import net.minecraft.server.network.ConnectedClientData; 12 | import net.minecraft.server.network.ServerPlayerEntity; 13 | import net.minecraft.text.Text; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.Redirect; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 21 | 22 | import fi.dy.masa.servux.event.PlayerHandler; 23 | 24 | /** 25 | * Interface for processing various server side Player Manager events 26 | */ 27 | @Mixin(PlayerManager.class) 28 | public abstract class MixinPlayerManager 29 | { 30 | @Unique 31 | private PlayerConfigEntry profileTemp; 32 | 33 | public MixinPlayerManager() { super(); } 34 | 35 | @Inject(method = "checkCanJoin", at = @At("RETURN")) 36 | private void servux_onClientConnect(SocketAddress address, PlayerConfigEntry playerConfigEntry, CallbackInfoReturnable cir) 37 | { 38 | ((PlayerHandler) PlayerHandler.getInstance()).onClientConnect(address, playerConfigEntry, cir.getReturnValue()); 39 | } 40 | 41 | @Inject(method = "onPlayerConnect", at = @At("TAIL")) 42 | private void servux_onPlayerJoin(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) 43 | { 44 | ((PlayerHandler) PlayerHandler.getInstance()).onPlayerJoin(connection.getAddress(), clientData.gameProfile(), player); 45 | } 46 | 47 | @Inject(method = "respawnPlayer", at = @At("RETURN")) 48 | private void servux_onPlayerRespawn(ServerPlayerEntity player, boolean alive, Entity.RemovalReason removalReason, CallbackInfoReturnable cir) 49 | { 50 | ((PlayerHandler) PlayerHandler.getInstance()).onPlayerRespawn(cir.getReturnValue(), player); 51 | } 52 | 53 | @Inject(method = "addToOperators(Lnet/minecraft/server/PlayerConfigEntry;Ljava/util/Optional;Ljava/util/Optional;)V", at = @At("HEAD")) 54 | private void servux_onCaptureGameProfileOp(PlayerConfigEntry player, Optional permissionLevel, Optional canBypassPlayerLimit, CallbackInfo ci) 55 | { 56 | this.profileTemp = player; 57 | } 58 | 59 | @Redirect(method = "addToOperators(Lnet/minecraft/server/PlayerConfigEntry;Ljava/util/Optional;Ljava/util/Optional;)V", 60 | at = @At(value = "INVOKE", 61 | target ="Lnet/minecraft/server/PlayerManager;getPlayer(Ljava/util/UUID;)Lnet/minecraft/server/network/ServerPlayerEntity;")) 62 | private ServerPlayerEntity servux_onPlayerOp(PlayerManager instance, UUID uuid) 63 | { 64 | ServerPlayerEntity player = instance.getPlayer(uuid); 65 | 66 | ((PlayerHandler) PlayerHandler.getInstance()).onPlayerOp(this.profileTemp, uuid, player); 67 | 68 | if (this.profileTemp != null) 69 | { 70 | this.profileTemp = null; 71 | } 72 | 73 | return player; 74 | } 75 | 76 | @Inject(method = "removeFromOperators", at = @At("HEAD")) 77 | private void servux_onGameProfileDeOp(PlayerConfigEntry player, CallbackInfo ci) 78 | { 79 | this.profileTemp = player; 80 | } 81 | 82 | @Redirect(method = "removeFromOperators", 83 | at = @At(value = "INVOKE", 84 | target="Lnet/minecraft/server/PlayerManager;getPlayer(Ljava/util/UUID;)Lnet/minecraft/server/network/ServerPlayerEntity;")) 85 | private ServerPlayerEntity servux_onPlayerDeOp(PlayerManager instance, UUID uuid) 86 | { 87 | ServerPlayerEntity player = instance.getPlayer(uuid); 88 | 89 | ((PlayerHandler) PlayerHandler.getInstance()).onPlayerDeOp(this.profileTemp, uuid, player); 90 | 91 | if (this.profileTemp != null) 92 | { 93 | this.profileTemp = null; 94 | } 95 | 96 | return player; 97 | } 98 | 99 | @Inject(method = "remove", at = @At("HEAD")) 100 | private void servux_onPlayerLeave(ServerPlayerEntity player, CallbackInfo ci) 101 | { 102 | ((PlayerHandler) PlayerHandler.getInstance()).onPlayerLeave(player); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/world/IMixinWorldTickScheduler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.world; 2 | 3 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap; 4 | 5 | import net.minecraft.world.tick.ChunkTickScheduler; 6 | import net.minecraft.world.tick.WorldTickScheduler; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.gen.Accessor; 9 | 10 | @Mixin(WorldTickScheduler.class) 11 | public interface IMixinWorldTickScheduler 12 | { 13 | @Accessor("chunkTickSchedulers") 14 | Long2ObjectMap> servux_getChunkTickSchedulers(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/world/MixinServerChunkLoadingManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.world; 2 | 3 | import org.spongepowered.asm.mixin.Mixin; 4 | import org.spongepowered.asm.mixin.injection.At; 5 | import org.spongepowered.asm.mixin.injection.Inject; 6 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 7 | 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.world.ServerChunkLoadingManager; 10 | import net.minecraft.world.chunk.WorldChunk; 11 | 12 | import fi.dy.masa.servux.dataproviders.StructureDataProvider; 13 | 14 | @Mixin(ServerChunkLoadingManager.class) 15 | public abstract class MixinServerChunkLoadingManager 16 | { 17 | @Inject(method = "track(Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/world/chunk/WorldChunk;)V", 18 | at = @At("HEAD")) 19 | private static void servux_onSendChunkPacket(ServerPlayerEntity player, 20 | WorldChunk chunk, 21 | CallbackInfo ci) 22 | { 23 | if (StructureDataProvider.INSTANCE.isEnabled()) 24 | { 25 | StructureDataProvider.INSTANCE.onStartedWatchingChunk(player, chunk); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/world/MixinServerWorld.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.world; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import fi.dy.masa.servux.dataproviders.HudDataProvider; 5 | import net.minecraft.server.MinecraftServer; 6 | import net.minecraft.server.world.ServerWorld; 7 | import net.minecraft.world.WorldProperties; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(ServerWorld.class) 16 | public abstract class MixinServerWorld 17 | { 18 | // @Shadow private int spawnChunkRadius; 19 | @Shadow @NotNull public abstract MinecraftServer getServer(); 20 | 21 | @Inject(method = "setSpawnPoint", at = @At("TAIL")) 22 | private void servux_onSetSpawnPos(WorldProperties.SpawnPoint spawnPoint, CallbackInfo ci) 23 | { 24 | if (HudDataProvider.INSTANCE.isEnabled()) 25 | { 26 | HudDataProvider.INSTANCE.setSpawnPos(spawnPoint.globalPos()); 27 | // HudDataProvider.INSTANCE.setSpawnChunkRadius((this.spawnChunkRadius - 1)); 28 | } 29 | } 30 | 31 | @Inject(method = "tickWeather()V", at = @At(value = "INVOKE", 32 | target = "Lnet/minecraft/world/level/ServerWorldProperties;setRaining(Z)V")) 33 | private void servux_onTickWeather(CallbackInfo ci, 34 | @Local(ordinal = 0) int i, @Local(ordinal = 1) int j, @Local(ordinal = 2) int k, 35 | @Local(ordinal = 1) boolean bl2, @Local(ordinal = 2) boolean bl3) 36 | { 37 | /* 38 | this.worldProperties.setThunderTime(j); 39 | this.worldProperties.setRainTime(k); 40 | this.worldProperties.setClearWeatherTime(i); 41 | this.worldProperties.setThundering(bl2); 42 | this.worldProperties.setRaining(bl3); 43 | */ 44 | 45 | if (HudDataProvider.INSTANCE.isEnabled()) 46 | { 47 | HudDataProvider.INSTANCE.tickWeather(i, k, j, bl3, bl2); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/world/MixinWorldChunk_UpdateSuppression.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.world; 2 | 3 | import fi.dy.masa.servux.util.WorldUtils; 4 | import net.minecraft.world.World; 5 | import net.minecraft.world.chunk.WorldChunk; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | import org.spongepowered.asm.mixin.injection.Slice; 10 | 11 | @Mixin(WorldChunk.class) 12 | public abstract class MixinWorldChunk_UpdateSuppression 13 | { 14 | @Redirect(method = "setBlockState", 15 | slice = @Slice(from = @At(value = "INVOKE", 16 | target = "Lnet/minecraft/world/chunk/ChunkSection;getBlockState(III)" + 17 | "Lnet/minecraft/block/BlockState;")), 18 | at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;isClient()Z", ordinal = 0)) 19 | private boolean servux_redirectIsRemote(World world) 20 | { 21 | return WorldUtils.shouldPreventBlockUpdates(world) || world.isClient(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/mixin/world/MixinWorld_UpdateSuppression.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.mixin.world; 2 | 3 | import fi.dy.masa.servux.util.IWorldUpdateSuppressor; 4 | import net.minecraft.world.World; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | 8 | @Mixin(World.class) 9 | public class MixinWorld_UpdateSuppression implements IWorldUpdateSuppressor 10 | { 11 | @Unique private boolean servux_preventBlockUpdates; 12 | 13 | @Override 14 | public boolean servux_getShouldPreventBlockUpdates() 15 | { 16 | return this.servux_preventBlockUpdates; 17 | } 18 | 19 | @Override 20 | public void servux_setShouldPreventBlockUpdates(boolean preventUpdates) 21 | { 22 | this.servux_preventBlockUpdates = preventUpdates; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/network/IServerPayloadData.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.network; 2 | 3 | import javax.annotation.Nullable; 4 | import net.minecraft.network.PacketByteBuf; 5 | 6 | /** 7 | * A Helper Interface for designing a Payload Encoder/Decoder class with some common functions 8 | */ 9 | public interface IServerPayloadData 10 | { 11 | /** 12 | * Returns a numerical version for your Protocol 13 | * @return (The Version) 14 | */ 15 | int getVersion(); 16 | 17 | /** 18 | * Returns a common packet "Type" value 19 | * @return (The Packet Type) 20 | */ 21 | int getPacketType(); 22 | 23 | /** 24 | * Returns the total size (in bytes) of this Data 25 | * @return (The implementation's Data Allocation Footprint) 26 | */ 27 | int getTotalSize(); 28 | 29 | /** 30 | * Informs if this data is currently in use 31 | * @return (True/False) 32 | */ 33 | boolean isEmpty(); 34 | 35 | /** 36 | * PacketByteBuf Decoder -- How this Data is converted FROM a PacketByteBuf 37 | * [NOTE]: In order for this to work, it needs to call a static version of this. 38 | * This version is only for guidance. 39 | * - 40 | * @param input (Incoming Packet) 41 | * @return (A new instance of the implementation class) 42 | * @param (The implementation class) 43 | */ 44 | @Nullable 45 | static T fromPacket(PacketByteBuf input) { return null; } 46 | 47 | /** 48 | * PacketByteBuf Encoder -- How this Data is converted TO a PacketByteBuf 49 | * @param output (A new Pooled Buffer containing this implementation's data) 50 | */ 51 | void toPacket(PacketByteBuf output); 52 | 53 | /** 54 | * Clear / Reset any values that need to be cleared 55 | */ 56 | void clear(); 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/network/IServerPlayHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.network; 2 | 3 | import net.minecraft.network.packet.CustomPayload; 4 | 5 | public interface IServerPlayHandler 6 | { 7 |

void registerServerPlayHandler(IPluginServerPlayHandler

handler); 8 |

void unregisterServerPlayHandler(IPluginServerPlayHandler

handler); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/network/PacketSplitter.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.network; 2 | 3 | import javax.annotation.Nullable; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import io.netty.buffer.Unpooled; 7 | import net.minecraft.network.PacketByteBuf; 8 | import net.minecraft.network.packet.CustomPayload; 9 | import net.minecraft.server.network.ServerPlayNetworkHandler; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | import net.minecraft.util.Identifier; 12 | 13 | /** 14 | * Network packet splitter code from QuickCarpet by skyrising 15 | * @author skyrising 16 | * 17 | * Updated by Sakura to work with newer versions by changing the Reading Session keys, 18 | * and using the HANDLER interface to send packets via the Payload system 19 | */ 20 | public class PacketSplitter 21 | { 22 | public static final int MAX_TOTAL_PER_PACKET_S2C = 1048576; 23 | public static final int MAX_PAYLOAD_PER_PACKET_S2C = MAX_TOTAL_PER_PACKET_S2C - 5; 24 | public static final int MAX_TOTAL_PER_PACKET_C2S = 32767; 25 | public static final int MAX_PAYLOAD_PER_PACKET_C2S = MAX_TOTAL_PER_PACKET_C2S - 5; 26 | public static final int DEFAULT_MAX_RECEIVE_SIZE_C2S = 1048576; 27 | public static final int DEFAULT_MAX_RECEIVE_SIZE_S2C = 67108864; 28 | 29 | private static final Map READING_SESSIONS = new HashMap<>(); 30 | 31 | public static boolean send(IPluginServerPlayHandler handler, PacketByteBuf packet, ServerPlayerEntity player, ServerPlayNetworkHandler networkHandler) 32 | { 33 | return send(handler, packet, MAX_PAYLOAD_PER_PACKET_S2C, player, networkHandler); 34 | } 35 | 36 | private static boolean send(IPluginServerPlayHandler handler, PacketByteBuf packet, int payloadLimit, ServerPlayerEntity player, ServerPlayNetworkHandler networkHandler) 37 | { 38 | int len = packet.writerIndex(); 39 | 40 | packet.resetReaderIndex(); 41 | 42 | for (int offset = 0; offset < len; offset += payloadLimit) 43 | { 44 | int thisLen = Math.min(len - offset, payloadLimit); 45 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer(thisLen)); 46 | 47 | buf.resetWriterIndex(); 48 | 49 | if (offset == 0) 50 | { 51 | buf.writeVarInt(len); 52 | } 53 | 54 | buf.writeBytes(packet, thisLen); 55 | handler.encodeWithSplitter(player, buf, networkHandler); 56 | } 57 | 58 | packet.release(); 59 | 60 | return true; 61 | } 62 | 63 | public static PacketByteBuf receive(IPluginServerPlayHandler handler, 64 | long key, 65 | PacketByteBuf buf) 66 | { 67 | // this size needed to be bumped larger for Litematics 68 | return receive(handler.getPayloadChannel(), key, buf, DEFAULT_MAX_RECEIVE_SIZE_S2C); 69 | } 70 | 71 | @Nullable 72 | private static PacketByteBuf receive(Identifier channel, 73 | long key, 74 | PacketByteBuf buf, 75 | int maxLength) 76 | { 77 | return READING_SESSIONS.computeIfAbsent(key, ReadingSession::new).receive(buf, maxLength); 78 | } 79 | 80 | // Not needed 81 | /* 82 | public static PacketByteBuf readPayload(PacketByteBuf byteBuf) 83 | { 84 | PacketByteBuf newBuf = new PacketByteBuf(Unpooled.buffer()); 85 | newBuf.writeBytes(byteBuf.copy()); 86 | byteBuf.skipBytes(byteBuf.readableBytes()); 87 | return newBuf; 88 | } 89 | 90 | ** 91 | * Sends a packet type ID as a VarInt, and then the given Compound tag. 92 | * 93 | public static void sendPacketTypeAndCompound(IPluginServerPlayHandler handler, int packetType, NbtCompound data, ServerPlayerEntity player, ServerPlayNetworkHandler networkHandler) 94 | { 95 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 96 | buf.writeVarInt(packetType); 97 | buf.writeNbt(data); 98 | 99 | send(handler, buf, player, networkHandler); 100 | } 101 | */ 102 | 103 | /** 104 | * I had to fix the `Pair.of` key mappings, because they were removed from MC; 105 | * So I made it into a pre-shared random session 'key' between client and server. 106 | * Generated using 'long key = Random.create(Util.getMeasuringTimeMs()).nextLong();' 107 | * - 108 | * It can be shared to the receiving end via a separate packet; or it can just be 109 | * generated randomly on the receiving end per an expected Reading Session. 110 | * It needs to be stored and changed for every unique session. 111 | */ 112 | private static class ReadingSession 113 | { 114 | private final long key; 115 | private int expectedSize = -1; 116 | private PacketByteBuf received; 117 | 118 | private ReadingSession(long key) 119 | { 120 | this.key = key; 121 | } 122 | 123 | @Nullable 124 | private PacketByteBuf receive(PacketByteBuf data, int maxLength) 125 | { 126 | data.readerIndex(0); 127 | //data = PacketUtils.slice(data); 128 | 129 | if (this.expectedSize < 0) 130 | { 131 | this.expectedSize = data.readVarInt(); 132 | 133 | if (this.expectedSize > maxLength) 134 | { 135 | throw new IllegalArgumentException("Payload too large"); 136 | } 137 | 138 | this.received = new PacketByteBuf(Unpooled.buffer(this.expectedSize)); 139 | } 140 | 141 | if (this.received == null) 142 | { 143 | throw new RuntimeException("Receive Buffer is empty"); 144 | } 145 | 146 | this.received.writeBytes(data.copy()); 147 | 148 | if (this.received.writerIndex() >= this.expectedSize) 149 | { 150 | READING_SESSIONS.remove(this.key); 151 | return this.received; 152 | } 153 | 154 | return null; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/network/ServerPlayHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.network; 2 | 3 | import com.google.common.collect.ArrayListMultimap; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import net.minecraft.network.packet.CustomPayload; 6 | import net.minecraft.util.Identifier; 7 | 8 | /** 9 | * The Server Network Play handler 10 | * @param (Payload) 11 | */ 12 | public class ServerPlayHandler implements IServerPlayHandler 13 | { 14 | private static final ServerPlayHandler INSTANCE = new ServerPlayHandler<>(); 15 | private final ArrayListMultimap> handlers = ArrayListMultimap.create(); 16 | public static IServerPlayHandler getInstance() 17 | { 18 | return INSTANCE; 19 | } 20 | 21 | private ServerPlayHandler() {} 22 | 23 | @Override 24 | @SuppressWarnings("unchecked") 25 | public

void registerServerPlayHandler(IPluginServerPlayHandler

handler) 26 | { 27 | Identifier channel = handler.getPayloadChannel(); 28 | 29 | if (this.handlers.containsEntry(channel, handler) == false) 30 | { 31 | this.handlers.put(channel, (IPluginServerPlayHandler) handler); 32 | } 33 | } 34 | 35 | @Override 36 | public

void unregisterServerPlayHandler(IPluginServerPlayHandler

handler) 37 | { 38 | Identifier channel = handler.getPayloadChannel(); 39 | 40 | if (this.handlers.remove(channel, handler)) 41 | { 42 | handler.reset(channel); 43 | handler.unregisterPlayReceiver(); 44 | } 45 | } 46 | 47 | @ApiStatus.Internal 48 | public void reset(Identifier channel) 49 | { 50 | if (this.handlers.isEmpty() == false) 51 | { 52 | for (IPluginServerPlayHandler handler : this.handlers.get(channel)) 53 | { 54 | handler.reset(channel); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/network/packet/ServuxHudHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.network.packet; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.UUID; 6 | import io.netty.buffer.Unpooled; 7 | 8 | import net.minecraft.network.PacketByteBuf; 9 | import net.minecraft.network.packet.CustomPayload; 10 | import net.minecraft.server.network.ServerPlayNetworkHandler; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.util.Identifier; 13 | import net.fabricmc.api.EnvType; 14 | import net.fabricmc.api.Environment; 15 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 16 | 17 | import fi.dy.masa.servux.Reference; 18 | import fi.dy.masa.servux.Servux; 19 | import fi.dy.masa.servux.dataproviders.HudDataProvider; 20 | import fi.dy.masa.servux.network.IPluginServerPlayHandler; 21 | import fi.dy.masa.servux.network.IServerPayloadData; 22 | import fi.dy.masa.servux.network.PacketSplitter; 23 | 24 | @Environment(EnvType.SERVER) 25 | public abstract class ServuxHudHandler implements IPluginServerPlayHandler 26 | { 27 | private static final ServuxHudHandler INSTANCE = new ServuxHudHandler<>() { 28 | @Override 29 | public void receive(ServuxHudPacket.Payload payload, ServerPlayNetworking.Context context) 30 | { 31 | ServuxHudHandler.INSTANCE.receivePlayPayload(payload, context); 32 | } 33 | }; 34 | public static ServuxHudHandler getInstance() { return INSTANCE; } 35 | 36 | public static final Identifier CHANNEL_ID = Identifier.of("servux", "hud_metadata"); 37 | 38 | private boolean payloadRegistered = false; 39 | private final Map failures = new HashMap<>(); 40 | private static final int MAX_FAILURES = 4; 41 | private final Map readingSessionKeys = new HashMap<>(); 42 | 43 | @Override 44 | public Identifier getPayloadChannel() { return CHANNEL_ID; } 45 | 46 | @Override 47 | public boolean isPlayRegistered(Identifier channel) 48 | { 49 | if (channel.equals(CHANNEL_ID)) 50 | { 51 | return payloadRegistered; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | @Override 58 | public void setPlayRegistered(Identifier channel) 59 | { 60 | if (channel.equals(CHANNEL_ID)) 61 | { 62 | this.payloadRegistered = true; 63 | } 64 | } 65 | 66 | @Override 67 | public

void decodeServerData(Identifier channel, ServerPlayerEntity player, P data) 68 | { 69 | ServuxHudPacket packet = (ServuxHudPacket) data; 70 | 71 | if (!channel.equals(CHANNEL_ID)) 72 | { 73 | return; 74 | } 75 | switch (packet.getType()) 76 | { 77 | case PACKET_C2S_METADATA_REQUEST -> HudDataProvider.INSTANCE.sendMetadata(player); 78 | case PACKET_C2S_SPAWN_DATA_REQUEST -> HudDataProvider.INSTANCE.refreshSpawnMetadata(player, packet.getCompound()); 79 | case PACKET_C2S_RECIPE_MANAGER_REQUEST -> HudDataProvider.INSTANCE.refreshRecipeManager(player, packet.getCompound()); 80 | case PACKET_C2S_DATA_LOGGER_REQUEST -> HudDataProvider.INSTANCE.refreshLoggers(player, packet.getCompound()); 81 | default -> Servux.LOGGER.warn("ServuxHudHandler#decodeServerData(): Invalid packetType '{}' from player: {}, of size in bytes: {}.", packet.getPacketType(), player.getName().getLiteralString(), packet.getTotalSize()); 82 | } 83 | } 84 | 85 | @Override 86 | public void reset(Identifier channel) 87 | { 88 | if (channel.equals(CHANNEL_ID)) 89 | { 90 | this.failures.clear(); 91 | } 92 | } 93 | 94 | public void resetFailures(Identifier channel, ServerPlayerEntity player) 95 | { 96 | if (channel.equals(CHANNEL_ID)) 97 | { 98 | this.failures.remove(player.getUuid()); 99 | } 100 | } 101 | 102 | @Override 103 | public void receivePlayPayload(T payload, ServerPlayNetworking.Context ctx) 104 | { 105 | if (payload.getId().id().equals(CHANNEL_ID)) 106 | { 107 | ServerPlayerEntity player = ctx.player(); 108 | ServuxHudHandler.INSTANCE.decodeServerData(CHANNEL_ID, player, ((ServuxHudPacket.Payload) payload).data()); 109 | } 110 | } 111 | 112 | @Override 113 | public void encodeWithSplitter(ServerPlayerEntity player, PacketByteBuf buffer, ServerPlayNetworkHandler networkHandler) 114 | { 115 | // Send each PacketSplitter buffer slice 116 | ServuxHudHandler.INSTANCE.sendPlayPayload(player, new ServuxHudPacket.Payload(ServuxHudPacket.ResponseS2CData(buffer))); 117 | } 118 | 119 | @Override 120 | public

void encodeServerData(ServerPlayerEntity player, P data) 121 | { 122 | if (!HudDataProvider.INSTANCE.isEnabled()) return; 123 | 124 | ServuxHudPacket packet = (ServuxHudPacket) data; 125 | 126 | // Send Response Data via Packet Splitter 127 | if (packet.getType().equals(ServuxHudPacket.Type.PACKET_S2C_NBT_RESPONSE_START)) 128 | { 129 | PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); 130 | buffer.writeNbt(packet.getCompound()); 131 | PacketSplitter.send(this, buffer, player, player.networkHandler); 132 | } 133 | else if (!ServuxHudHandler.INSTANCE.sendPlayPayload(player, new ServuxHudPacket.Payload(packet))) 134 | { 135 | UUID id = player.getUuid(); 136 | 137 | // Packet failure tracking 138 | if (!this.failures.containsKey(id)) 139 | { 140 | this.failures.put(id, 1); 141 | } 142 | else if (this.failures.get(id) > MAX_FAILURES) 143 | { 144 | if (Reference.DEV_DEBUG) 145 | { 146 | Servux.LOGGER.info("Unregistering Hud Data Client {} after {} failures (MiniHUD not installed perhaps)", player.getName().getLiteralString(), MAX_FAILURES); 147 | } 148 | 149 | HudDataProvider.INSTANCE.onPacketFailure(player); 150 | } 151 | else 152 | { 153 | int count = this.failures.get(id) + 1; 154 | this.failures.put(id, count); 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/SchematicSchema.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic; 2 | 3 | public record SchematicSchema(int litematicVersion, int minecraftDataVersion) 4 | { 5 | @Override 6 | public String toString() 7 | { 8 | return "V" + this.litematicVersion() + " / DataVersion " + this.minecraftDataVersion(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/container/ILitematicaBlockStatePalette.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.container; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.nbt.NbtList; 5 | 6 | import javax.annotation.Nullable; 7 | import java.util.List; 8 | 9 | public interface ILitematicaBlockStatePalette 10 | { 11 | /** 12 | * Gets the palette id for the given block state and adds 13 | * the state to the palette if it doesn't exist there yet. 14 | */ 15 | int idFor(BlockState state); 16 | 17 | /** 18 | * Gets the block state by the palette id. 19 | */ 20 | @Nullable 21 | BlockState getBlockState(int indexKey); 22 | 23 | int getPaletteSize(); 24 | 25 | void readFromNBT(NbtList tagList); 26 | 27 | NbtList writeToNBT(); 28 | 29 | /** 30 | * Sets the current mapping of the palette. 31 | * This is meant for reading the palette from file. 32 | * @param list 33 | * @return true if the mapping was set successfully, false if it failed 34 | */ 35 | boolean setMapping(List list); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/container/ILitematicaBlockStatePaletteResizer.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.container; 2 | 3 | import net.minecraft.block.BlockState; 4 | 5 | public interface ILitematicaBlockStatePaletteResizer 6 | { 7 | int onResize(int bits, BlockState state); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/container/LitematicaBitArray.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.container; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | public class LitematicaBitArray 8 | { 9 | /** The long array that is used to store the data for this BitArray. */ 10 | private final long[] longArray; 11 | /** Number of bits a single entry takes up */ 12 | private final int bitsPerEntry; 13 | /** 14 | * The maximum value for a single entry. This also works as a bitmask for a single entry. 15 | * For instance, if bitsPerEntry were 5, this value would be 31 (ie, {@code 0b00011111}). 16 | */ 17 | private final long maxEntryValue; 18 | /** Number of entries in this array (not the length of the long array that internally backs this array) */ 19 | private final long arraySize; 20 | 21 | public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn) 22 | { 23 | this(bitsPerEntryIn, arraySizeIn, null); 24 | } 25 | 26 | public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn, @Nullable long[] longArrayIn) 27 | { 28 | Validate.inclusiveBetween(1L, 32L, bitsPerEntryIn); 29 | this.arraySize = arraySizeIn; 30 | this.bitsPerEntry = bitsPerEntryIn; 31 | this.maxEntryValue = (1L << bitsPerEntryIn) - 1L; 32 | 33 | if (longArrayIn != null) 34 | { 35 | this.longArray = longArrayIn; 36 | } 37 | else 38 | { 39 | this.longArray = new long[(int) (roundUp(arraySizeIn * bitsPerEntryIn, 64L) / 64L)]; 40 | } 41 | } 42 | 43 | public void setAt(long index, int value) 44 | { 45 | //Validate.inclusiveBetween(0L, this.arraySize - 1L, index); 46 | //Validate.inclusiveBetween(0L, this.maxEntryValue, value); 47 | long startOffset = index * (long) this.bitsPerEntry; 48 | int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 49 | int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); 50 | int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64 51 | this.longArray[startArrIndex] = this.longArray[startArrIndex] & ~(this.maxEntryValue << startBitOffset) | ((long) value & this.maxEntryValue) << startBitOffset; 52 | 53 | if (startArrIndex != endArrIndex) 54 | { 55 | int endOffset = 64 - startBitOffset; 56 | int j1 = this.bitsPerEntry - endOffset; 57 | this.longArray[endArrIndex] = this.longArray[endArrIndex] >>> j1 << j1 | ((long) value & this.maxEntryValue) >> endOffset; 58 | } 59 | } 60 | 61 | public int getAt(long index) 62 | { 63 | //Validate.inclusiveBetween(0L, this.arraySize - 1L, index); 64 | long startOffset = index * (long) this.bitsPerEntry; 65 | int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 66 | int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); 67 | int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64 68 | 69 | if (startArrIndex == endArrIndex) 70 | { 71 | return (int) (this.longArray[startArrIndex] >>> startBitOffset & this.maxEntryValue); 72 | } 73 | else 74 | { 75 | int endOffset = 64 - startBitOffset; 76 | return (int) ((this.longArray[startArrIndex] >>> startBitOffset | this.longArray[endArrIndex] << endOffset) & this.maxEntryValue); 77 | } 78 | } 79 | 80 | public long[] getBackingLongArray() 81 | { 82 | return this.longArray; 83 | } 84 | 85 | public long size() 86 | { 87 | return this.arraySize; 88 | } 89 | 90 | public static long roundUp(long value, long interval) 91 | { 92 | if (interval == 0L) 93 | { 94 | return 0L; 95 | } 96 | else if (value == 0L) 97 | { 98 | return interval; 99 | } 100 | else 101 | { 102 | if (value < 0L) 103 | { 104 | interval *= -1L; 105 | } 106 | 107 | long remainder = value % interval; 108 | 109 | return remainder == 0L ? value : value + interval - remainder; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/container/LitematicaBlockStatePaletteHashMap.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.container; 2 | 3 | import java.util.List; 4 | import javax.annotation.Nullable; 5 | 6 | import net.minecraft.block.Block; 7 | import net.minecraft.block.BlockState; 8 | import net.minecraft.nbt.NbtCompound; 9 | import net.minecraft.nbt.NbtHelper; 10 | import net.minecraft.nbt.NbtList; 11 | import net.minecraft.registry.RegistryEntryLookup; 12 | import net.minecraft.registry.RegistryKeys; 13 | import net.minecraft.util.collection.Int2ObjectBiMap; 14 | 15 | import fi.dy.masa.servux.dataproviders.DataProviderManager; 16 | 17 | public class LitematicaBlockStatePaletteHashMap implements ILitematicaBlockStatePalette 18 | { 19 | private final Int2ObjectBiMap statePaletteMap; 20 | private final ILitematicaBlockStatePaletteResizer paletteResizer; 21 | private final int bits; 22 | 23 | public LitematicaBlockStatePaletteHashMap(int bitsIn, ILitematicaBlockStatePaletteResizer paletteResizer) 24 | { 25 | this.bits = bitsIn; 26 | this.paletteResizer = paletteResizer; 27 | this.statePaletteMap = Int2ObjectBiMap.create(1 << bitsIn); 28 | } 29 | 30 | @Override 31 | public int idFor(BlockState state) 32 | { 33 | int i = this.statePaletteMap.getRawId(state); 34 | 35 | if (i == -1) 36 | { 37 | i = this.statePaletteMap.add(state); 38 | 39 | if (i >= (1 << this.bits)) 40 | { 41 | i = this.paletteResizer.onResize(this.bits + 1, state); 42 | } 43 | } 44 | 45 | return i; 46 | } 47 | 48 | @Override 49 | @Nullable 50 | public BlockState getBlockState(int indexKey) 51 | { 52 | return this.statePaletteMap.get(indexKey); 53 | } 54 | 55 | @Override 56 | public int getPaletteSize() 57 | { 58 | return this.statePaletteMap.size(); 59 | } 60 | 61 | private void requestNewId(BlockState state) 62 | { 63 | final int origId = this.statePaletteMap.add(state); 64 | 65 | if (origId >= (1 << this.bits)) 66 | { 67 | int newId = this.paletteResizer.onResize(this.bits + 1, LitematicaBlockStateContainer.AIR_BLOCK_STATE); 68 | 69 | if (newId <= origId) 70 | { 71 | this.statePaletteMap.add(state); 72 | } 73 | } 74 | } 75 | 76 | @Override 77 | public void readFromNBT(NbtList tagList) 78 | { 79 | //RegistryEntryLookup lookup = Registries.BLOCK.getReadOnlyWrapper(); 80 | RegistryEntryLookup lookup = DataProviderManager.INSTANCE.getRegistryManager().getOrThrow(RegistryKeys.BLOCK); 81 | // Ugly, but it should work, without changing the ILitematicaBlockStatePalette interface. 82 | 83 | final int size = tagList.size(); 84 | 85 | for (int i = 0; i < size; ++i) 86 | { 87 | NbtCompound tag = tagList.getCompoundOrEmpty(i); 88 | BlockState state = NbtHelper.toBlockState(lookup, tag); 89 | 90 | if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) 91 | { 92 | this.requestNewId(state); 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public NbtList writeToNBT() 99 | { 100 | NbtList tagList = new NbtList(); 101 | 102 | for (int id = 0; id < this.statePaletteMap.size(); ++id) 103 | { 104 | BlockState state = this.statePaletteMap.get(id); 105 | 106 | if (state == null) 107 | { 108 | state = LitematicaBlockStateContainer.AIR_BLOCK_STATE; 109 | } 110 | 111 | NbtCompound tag = NbtHelper.fromBlockState(state); 112 | tagList.add(tag); 113 | } 114 | 115 | return tagList; 116 | } 117 | 118 | @Override 119 | public boolean setMapping(List list) 120 | { 121 | this.statePaletteMap.clear(); 122 | 123 | for (BlockState blockState : list) 124 | { 125 | this.statePaletteMap.add(blockState); 126 | } 127 | 128 | return true; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/container/LitematicaBlockStatePaletteLinear.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.container; 2 | 3 | import java.util.List; 4 | import javax.annotation.Nullable; 5 | 6 | import net.minecraft.block.Block; 7 | import net.minecraft.block.BlockState; 8 | import net.minecraft.nbt.NbtCompound; 9 | import net.minecraft.nbt.NbtHelper; 10 | import net.minecraft.nbt.NbtList; 11 | import net.minecraft.registry.RegistryEntryLookup; 12 | import net.minecraft.registry.RegistryKeys; 13 | 14 | import fi.dy.masa.servux.dataproviders.DataProviderManager; 15 | 16 | public class LitematicaBlockStatePaletteLinear implements ILitematicaBlockStatePalette 17 | { 18 | private final BlockState[] states; 19 | private final ILitematicaBlockStatePaletteResizer resizeHandler; 20 | private final int bits; 21 | private int currentSize; 22 | 23 | public LitematicaBlockStatePaletteLinear(int bitsIn, ILitematicaBlockStatePaletteResizer resizeHandler) 24 | { 25 | this.states = new BlockState[1 << bitsIn]; 26 | this.bits = bitsIn; 27 | this.resizeHandler = resizeHandler; 28 | } 29 | 30 | @Override 31 | public int idFor(BlockState state) 32 | { 33 | for (int i = 0; i < this.currentSize; ++i) 34 | { 35 | if (this.states[i] == state) 36 | { 37 | return i; 38 | } 39 | } 40 | 41 | final int size = this.currentSize; 42 | 43 | if (size < this.states.length) 44 | { 45 | this.states[size] = state; 46 | ++this.currentSize; 47 | return size; 48 | } 49 | else 50 | { 51 | return this.resizeHandler.onResize(this.bits + 1, state); 52 | } 53 | } 54 | 55 | @Override 56 | @Nullable 57 | public BlockState getBlockState(int indexKey) 58 | { 59 | return indexKey >= 0 && indexKey < this.currentSize ? this.states[indexKey] : null; 60 | } 61 | 62 | @Override 63 | public int getPaletteSize() 64 | { 65 | return this.currentSize; 66 | } 67 | 68 | private void requestNewId(BlockState state) 69 | { 70 | final int size = this.currentSize; 71 | 72 | if (size < this.states.length) 73 | { 74 | this.states[size] = state; 75 | ++this.currentSize; 76 | } 77 | else 78 | { 79 | int newId = this.resizeHandler.onResize(this.bits + 1, LitematicaBlockStateContainer.AIR_BLOCK_STATE); 80 | 81 | if (newId <= size) 82 | { 83 | this.states[size] = state; 84 | ++this.currentSize; 85 | } 86 | } 87 | } 88 | 89 | @Override 90 | public void readFromNBT(NbtList tagList) 91 | { 92 | //RegistryEntryLookup lookup = Registries.BLOCK.getReadOnlyWrapper(); 93 | RegistryEntryLookup lookup = DataProviderManager.INSTANCE.getRegistryManager().getOrThrow(RegistryKeys.BLOCK); 94 | // Ugly, but it should work, without changing the ILitematicaBlockStatePalette interface. 95 | 96 | final int size = tagList.size(); 97 | 98 | for (int i = 0; i < size; ++i) 99 | { 100 | NbtCompound tag = tagList.getCompoundOrEmpty(i); 101 | BlockState state = NbtHelper.toBlockState(lookup, tag); 102 | 103 | if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) 104 | { 105 | this.requestNewId(state); 106 | } 107 | } 108 | } 109 | 110 | @Override 111 | public NbtList writeToNBT() 112 | { 113 | NbtList tagList = new NbtList(); 114 | 115 | for (int id = 0; id < this.currentSize; ++id) 116 | { 117 | BlockState state = this.states[id]; 118 | 119 | if (state == null) 120 | { 121 | state = LitematicaBlockStateContainer.AIR_BLOCK_STATE; 122 | } 123 | 124 | NbtCompound tag = NbtHelper.fromBlockState(state); 125 | tagList.add(tag); 126 | } 127 | 128 | return tagList; 129 | } 130 | 131 | @Override 132 | public boolean setMapping(List list) 133 | { 134 | final int size = list.size(); 135 | 136 | if (size <= this.states.length) 137 | { 138 | for (int id = 0; id < size; ++id) 139 | { 140 | this.states[id] = list.get(id); 141 | } 142 | 143 | this.currentSize = size; 144 | 145 | return true; 146 | } 147 | 148 | return false; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/selection/AreaSelectionSimple.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.selection; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import fi.dy.masa.servux.util.JsonUtils; 7 | import net.minecraft.util.math.BlockPos; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | public class AreaSelectionSimple extends AreaSelection 12 | { 13 | public AreaSelectionSimple(boolean createDefaultBox) 14 | { 15 | if (createDefaultBox) 16 | { 17 | this.createDefaultBoxIfNeeded(); 18 | } 19 | } 20 | 21 | @Override 22 | public boolean setSelectedSubRegionBox(String name) 23 | { 24 | // NO-OP 25 | return false; 26 | } 27 | 28 | @Override 29 | @Nullable 30 | public String createNewSubRegionBox(BlockPos pos1, String nameIn) 31 | { 32 | // NO-OP 33 | return null; 34 | } 35 | 36 | @Override 37 | public boolean addSubRegionBox(Box box, boolean replace) 38 | { 39 | // NO-OP 40 | return false; 41 | } 42 | 43 | @Override 44 | public void removeAllSubRegionBoxes() 45 | { 46 | // NO-OP 47 | } 48 | 49 | @Override 50 | public boolean removeSubRegionBox(String name) 51 | { 52 | // NO-OP 53 | return false; 54 | } 55 | 56 | private void createDefaultBoxIfNeeded() 57 | { 58 | if (this.subRegionBoxes.size() != 1) 59 | { 60 | this.subRegionBoxes.clear(); 61 | Box box = new Box(BlockPos.ORIGIN, BlockPos.ORIGIN, this.getName()); 62 | this.subRegionBoxes.put(box.getName(), box); 63 | this.currentBox = box.getName(); 64 | } 65 | else if (this.currentBox == null || this.subRegionBoxes.get(this.currentBox) == null) 66 | { 67 | this.currentBox = this.subRegionBoxes.keySet().iterator().next(); 68 | } 69 | } 70 | 71 | public AreaSelectionSimple copy() 72 | { 73 | return fromJson(this.toJson()); 74 | } 75 | 76 | public static AreaSelectionSimple fromJson(JsonObject obj) 77 | { 78 | AreaSelectionSimple area = new AreaSelectionSimple(false); 79 | 80 | if (JsonUtils.hasArray(obj, "boxes")) 81 | { 82 | JsonArray arr = obj.get("boxes").getAsJsonArray(); 83 | 84 | if (arr.size() > 0) 85 | { 86 | // The simple area will only have one box 87 | JsonElement el = arr.get(0); 88 | 89 | if (el.isJsonObject()) 90 | { 91 | Box box = Box.fromJson(el.getAsJsonObject()); 92 | 93 | if (box != null) 94 | { 95 | area.subRegionBoxes.put(box.getName(), box); 96 | area.currentBox = box.getName(); 97 | } 98 | } 99 | } 100 | } 101 | 102 | if (JsonUtils.hasString(obj, "name")) 103 | { 104 | area.setName(obj.get("name").getAsString()); 105 | } 106 | 107 | BlockPos pos = JsonUtils.blockPosFromJson(obj, "origin"); 108 | 109 | if (pos != null) 110 | { 111 | area.setExplicitOrigin(pos); 112 | } 113 | else 114 | { 115 | area.updateCalculatedOrigin(); 116 | } 117 | 118 | // Make sure the simple area has exactly one box, and that it's selected 119 | area.createDefaultBoxIfNeeded(); 120 | 121 | return area; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/selection/BoxSliced.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.selection; 2 | 3 | import net.minecraft.util.math.Direction; 4 | 5 | public class BoxSliced extends Box 6 | { 7 | private Direction sliceDirection = Direction.EAST; 8 | private int sliceStart = 0; 9 | private int sliceEnd = 1; 10 | private int sliceCount; 11 | 12 | public Direction getSliceDirection() 13 | { 14 | return sliceDirection; 15 | } 16 | 17 | /** 18 | * Returns the inclusive relative start offset from pos1 19 | * @return 20 | */ 21 | public int getSliceStart() 22 | { 23 | return sliceStart; 24 | } 25 | 26 | /** 27 | * Returns the exclusive relative end offset from pos1 28 | * @return 29 | */ 30 | public int getSliceEnd() 31 | { 32 | return sliceEnd; 33 | } 34 | 35 | public int getSliceCount() 36 | { 37 | return sliceCount; 38 | } 39 | 40 | public int getMaxSliceLength() 41 | { 42 | switch (this.sliceDirection.getAxis()) 43 | { 44 | case X: return this.getSize().getX(); 45 | case Y: return this.getSize().getY(); 46 | case Z: return this.getSize().getZ(); 47 | default: return 1; 48 | } 49 | } 50 | 51 | public void setSliceDirection(Direction sliceDirection) 52 | { 53 | this.sliceDirection = sliceDirection; 54 | } 55 | 56 | public void setSliceStart(int sliceStart) 57 | { 58 | this.sliceStart = Math.min(sliceStart, this.getMaxSliceLength() - 1); 59 | } 60 | 61 | public void setSliceEnd(int sliceEnd) 62 | { 63 | this.sliceEnd = Math.min(sliceEnd, this.getMaxSliceLength()); 64 | } 65 | 66 | public void setSliceCount(int sliceCount) 67 | { 68 | this.sliceCount = sliceCount; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/selection/SelectionManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.selection; 2 | 3 | import java.nio.file.Path; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import javax.annotation.Nullable; 7 | import com.google.gson.JsonElement; 8 | 9 | import fi.dy.masa.servux.util.JsonUtils; 10 | 11 | public class SelectionManager 12 | { 13 | private final Map selections = new HashMap<>(); 14 | private final Map readOnlySelections = new HashMap<>(); 15 | @Nullable 16 | private String currentSelectionId; 17 | private SelectionMode mode = SelectionMode.SIMPLE; 18 | 19 | @Nullable 20 | public String getCurrentSelectionId() 21 | { 22 | return this.mode == SelectionMode.NORMAL ? this.currentSelectionId : null; 23 | } 24 | 25 | @Nullable 26 | public String getCurrentNormalSelectionId() 27 | { 28 | return this.currentSelectionId; 29 | } 30 | 31 | @Nullable 32 | protected AreaSelection getNormalSelection(@Nullable String selectionId) 33 | { 34 | return selectionId != null ? this.selections.get(selectionId) : null; 35 | } 36 | 37 | @Nullable 38 | private AreaSelection tryLoadSelectionFromFile(String selectionId) 39 | { 40 | return tryLoadSelectionFromFile(Path.of(selectionId)); 41 | } 42 | 43 | @Nullable 44 | public static AreaSelection tryLoadSelectionFromFile(Path file) 45 | { 46 | JsonElement el = JsonUtils.parseJsonFileAsPath(file); 47 | 48 | if (el != null && el.isJsonObject()) 49 | { 50 | return AreaSelection.fromJson(el.getAsJsonObject()); 51 | } 52 | 53 | return null; 54 | } 55 | 56 | 57 | public void clear() 58 | { 59 | this.currentSelectionId = null; 60 | this.selections.clear(); 61 | this.readOnlySelections.clear(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/selection/SelectionMode.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.selection; 2 | 3 | 4 | public enum SelectionMode 5 | { 6 | NORMAL ("litematica.gui.label.area_selection.mode.normal"), 7 | SIMPLE ("litematica.gui.label.area_selection.mode.simple"); 8 | 9 | private final String translationKey; 10 | 11 | private SelectionMode(String translationKey) 12 | { 13 | this.translationKey = translationKey; 14 | } 15 | 16 | public String getTranslationKey() 17 | { 18 | return this.translationKey; 19 | } 20 | 21 | public String getDisplayName() 22 | { 23 | return (this.translationKey); 24 | } 25 | 26 | public SelectionMode cycle(boolean forward) 27 | { 28 | int id = this.ordinal(); 29 | 30 | if (forward) 31 | { 32 | if (++id >= values().length) 33 | { 34 | id = 0; 35 | } 36 | } 37 | else 38 | { 39 | if (--id < 0) 40 | { 41 | id = values().length - 1; 42 | } 43 | } 44 | 45 | return values()[id % values().length]; 46 | } 47 | 48 | public static SelectionMode fromString(String name) 49 | { 50 | for (SelectionMode mode : SelectionMode.values()) 51 | { 52 | if (mode.name().equalsIgnoreCase(name)) 53 | { 54 | return mode; 55 | } 56 | } 57 | 58 | return SelectionMode.NORMAL; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/transmit/SchematicBuffer.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.transmit; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.HashMap; 8 | 9 | import fi.dy.masa.servux.Servux; 10 | import fi.dy.masa.servux.util.data.FileType; 11 | 12 | public class SchematicBuffer implements AutoCloseable 13 | { 14 | public static final int BUFFER_SIZE = 16384; 15 | private final String name; 16 | private final FileType type; 17 | private final HashMap buffer; 18 | 19 | public SchematicBuffer(String name) 20 | { 21 | this(name, FileType.LITEMATICA_SCHEMATIC); 22 | } 23 | 24 | public SchematicBuffer(String name, FileType type) 25 | { 26 | this.name = name; 27 | this.type = type; 28 | this.buffer = new HashMap<>(); 29 | } 30 | 31 | public String getName() 32 | { 33 | return this.name; 34 | } 35 | 36 | public FileType getType() 37 | { 38 | return this.type; 39 | } 40 | 41 | public Path getFileName() 42 | { 43 | String ext = FileType.getFileExt(this.type); 44 | 45 | if (this.name.contains(ext)) 46 | { 47 | return Path.of(this.name); 48 | } 49 | else 50 | { 51 | return Path.of(this.name + ext); 52 | } 53 | } 54 | 55 | public void receiveSlice(final int number, Slice slice) 56 | { 57 | this.buffer.put(number, slice); 58 | } 59 | 60 | public Path writeFile(Path dir) 61 | { 62 | if (!Files.isDirectory(dir)) 63 | { 64 | try 65 | { 66 | Files.createDirectory(dir); 67 | Servux.debugLog("LitematicBuffer#writeFile(): Created directory '{}' successfully", dir.toAbsolutePath().toString()); 68 | } 69 | catch (IOException err) 70 | { 71 | Servux.LOGGER.error("LitematicBuffer#writeFile(): Exception creating directory '{}'; {}", dir.toAbsolutePath().toString(), err.getLocalizedMessage()); 72 | return null; 73 | } 74 | } 75 | 76 | Path file = dir.resolve(this.getFileName()); 77 | 78 | if (Files.exists(file)) 79 | { 80 | try 81 | { 82 | Files.delete(file); 83 | Servux.debugLog("LitematicBuffer#writeFile(): Deleted file '{}' successfully", file.toAbsolutePath().toString()); 84 | } 85 | catch (IOException err) 86 | { 87 | Servux.LOGGER.error("LitematicBuffer#writeFile(): Exception deleting file '{}'; {}", file.toAbsolutePath().toString(), err.getLocalizedMessage()); 88 | return null; 89 | } 90 | } 91 | 92 | try (OutputStream os = Files.newOutputStream(file)) 93 | { 94 | // Write in correct Slice order 95 | for (int i = 0; i < this.buffer.size(); i++) 96 | { 97 | Slice entry = this.buffer.get(i); 98 | 99 | if (entry != null) 100 | { 101 | os.write(entry.data(), 0, entry.size()); 102 | } 103 | } 104 | } 105 | catch (Exception err) 106 | { 107 | Servux.LOGGER.error("LitematicBuffer#writeFile(): Exception saving file '{}'; {}", file.toAbsolutePath().toString(), err.getLocalizedMessage()); 108 | return null; 109 | } 110 | 111 | Servux.debugLog("LitematicBuffer#writeFile(): Saved file '{}' successfully", file.toAbsolutePath().toString()); 112 | this.buffer.clear(); 113 | return file; 114 | } 115 | 116 | @Override 117 | public void close() throws Exception 118 | { 119 | this.buffer.clear(); 120 | } 121 | 122 | public record Slice(byte[] data, int size) {} 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/schematic/transmit/SchematicBufferManager.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.schematic.transmit; 2 | 3 | import java.nio.file.Path; 4 | import java.util.HashMap; 5 | import java.util.UUID; 6 | import javax.annotation.Nullable; 7 | 8 | import net.minecraft.nbt.NbtCompound; 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | 11 | import fi.dy.masa.servux.Servux; 12 | import fi.dy.masa.servux.dataproviders.LitematicsDataProvider; 13 | import fi.dy.masa.servux.schematic.LitematicaSchematic; 14 | import fi.dy.masa.servux.util.data.FileType; 15 | 16 | public class SchematicBufferManager 17 | { 18 | private final HashMap fileBuffers; 19 | private final HashMap optionalNbt; 20 | private final HashMap playerMap; 21 | 22 | public SchematicBufferManager() 23 | { 24 | this.fileBuffers = new HashMap<>(); 25 | this.optionalNbt = new HashMap<>(); 26 | this.playerMap = new HashMap<>(); 27 | } 28 | 29 | public void createBuffer(String name, final long sessionKey, ServerPlayerEntity player) 30 | { 31 | this.createBuffer(name, FileType.LITEMATICA_SCHEMATIC, sessionKey, null, player); 32 | } 33 | 34 | public void createBuffer(String name, final long sessionKey, @Nullable NbtCompound optional, ServerPlayerEntity player) 35 | { 36 | this.createBuffer(name, FileType.LITEMATICA_SCHEMATIC, sessionKey, optional, player); 37 | } 38 | 39 | public void createBuffer(String name, FileType type, final long sessionKey, @Nullable NbtCompound optional, ServerPlayerEntity player) 40 | { 41 | if (this.fileBuffers.containsKey(sessionKey) || this.optionalNbt.containsKey(sessionKey)) 42 | { 43 | Servux.LOGGER.warn("createBuffer: Cannot create a new buffer for an existing session key!"); 44 | return; 45 | } 46 | 47 | SchematicBuffer newBuf = new SchematicBuffer(name, type); 48 | this.fileBuffers.put(sessionKey, newBuf); 49 | 50 | if (optional != null && !optional.isEmpty()) 51 | { 52 | this.optionalNbt.put(sessionKey, optional.copy()); 53 | } 54 | 55 | this.playerMap.put(player.getUuid(), sessionKey); 56 | } 57 | 58 | private @Nullable SchematicBuffer getBuffer(final long sessionKey) 59 | { 60 | if (this.fileBuffers.containsKey(sessionKey)) 61 | { 62 | return this.fileBuffers.get(sessionKey); 63 | } 64 | 65 | return null; 66 | } 67 | 68 | public NbtCompound getOptionalNbt(final long sessionKey) 69 | { 70 | if (this.optionalNbt.containsKey(sessionKey)) 71 | { 72 | return this.optionalNbt.get(sessionKey); 73 | } 74 | 75 | return new NbtCompound(); 76 | } 77 | 78 | public void receiveSlice(final long sessionKey, final int slice, byte[] dataIn, final int size) 79 | { 80 | if (this.fileBuffers.containsKey(sessionKey)) 81 | { 82 | this.fileBuffers.get(sessionKey).receiveSlice(slice, new SchematicBuffer.Slice(dataIn, size)); 83 | } 84 | else 85 | { 86 | Servux.LOGGER.error("receiveSlice: Error; cannot receive a slice for a non-existing session"); 87 | } 88 | } 89 | 90 | public void cancelBuffer(final long sessionKey) 91 | { 92 | if (this.fileBuffers.containsKey(sessionKey)) 93 | { 94 | try (SchematicBuffer buffer = this.fileBuffers.remove(sessionKey)) 95 | { 96 | buffer.close(); 97 | } 98 | catch (Exception ignored) {} 99 | } 100 | 101 | this.optionalNbt.remove(sessionKey); 102 | } 103 | 104 | public void removePlayer(ServerPlayerEntity player) 105 | { 106 | UUID uuid = player.getUuid(); 107 | 108 | if (this.playerMap.containsKey(uuid)) 109 | { 110 | final long key = this.playerMap.get(uuid); 111 | this.cancelBuffer(key); 112 | this.playerMap.remove(uuid); 113 | } 114 | } 115 | 116 | public @Nullable LitematicaSchematic finishBuffer(final long sessionKey, @Nullable Path dir) 117 | { 118 | if (this.fileBuffers.containsKey(sessionKey)) 119 | { 120 | SchematicBuffer buffer = this.fileBuffers.get(sessionKey); 121 | 122 | if (dir == null) 123 | { 124 | dir = LitematicsDataProvider.INSTANCE.getTransmitDir(); 125 | } 126 | 127 | Path file = buffer.writeFile(dir); 128 | 129 | if (file == null) 130 | { 131 | Servux.LOGGER.error("finishBuffer: Failed writing Schematic Buffer to file: '{}'", buffer.getFileName()); 132 | return null; 133 | } 134 | 135 | LitematicaSchematic schematic = LitematicaSchematic.createFromFile(dir, buffer.getName(), buffer.getType()); 136 | this.cancelBuffer(sessionKey); 137 | return schematic; 138 | } 139 | 140 | return null; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/servux/PlayerListener.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.servux; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import fi.dy.masa.servux.dataproviders.*; 5 | import fi.dy.masa.servux.interfaces.IPlayerListener; 6 | import net.minecraft.server.network.ServerPlayerEntity; 7 | 8 | import java.net.SocketAddress; 9 | 10 | public class PlayerListener implements IPlayerListener 11 | { 12 | @Override 13 | public void onPlayerJoin(SocketAddress addr, GameProfile profile, ServerPlayerEntity player) 14 | { 15 | if (HudDataProvider.INSTANCE.isEnabled()) 16 | { 17 | HudDataProvider.INSTANCE.sendMetadata(player); 18 | } 19 | 20 | if (StructureDataProvider.INSTANCE.isEnabled()) 21 | { 22 | StructureDataProvider.INSTANCE.register(player); 23 | } 24 | 25 | if (EntitiesDataProvider.INSTANCE.isEnabled()) 26 | { 27 | EntitiesDataProvider.INSTANCE.sendMetadata(player); 28 | } 29 | 30 | if (LitematicsDataProvider.INSTANCE.isEnabled()) 31 | { 32 | LitematicsDataProvider.INSTANCE.sendMetadata(player); 33 | } 34 | 35 | if (TweaksDataProvider.INSTANCE.isEnabled()) 36 | { 37 | TweaksDataProvider.INSTANCE.sendMetadata(player); 38 | } 39 | 40 | // if (DebugDataProvider.INSTANCE.isEnabled()) 41 | // { 42 | // DebugDataProvider.INSTANCE.register(player); 43 | // } 44 | } 45 | 46 | @Override 47 | public void onPlayerLeave(ServerPlayerEntity player) 48 | { 49 | if (HudDataProvider.INSTANCE.isEnabled()) 50 | { 51 | HudDataProvider.INSTANCE.removePlayer(player); 52 | } 53 | 54 | if (StructureDataProvider.INSTANCE.isEnabled()) 55 | { 56 | StructureDataProvider.INSTANCE.unregister(player); 57 | } 58 | 59 | if (EntitiesDataProvider.INSTANCE.isEnabled()) 60 | { 61 | EntitiesDataProvider.INSTANCE.removePlayer(player); 62 | } 63 | 64 | if (LitematicsDataProvider.INSTANCE.isEnabled()) 65 | { 66 | LitematicsDataProvider.INSTANCE.removePlayer(player); 67 | } 68 | 69 | if (TweaksDataProvider.INSTANCE.isEnabled()) 70 | { 71 | TweaksDataProvider.INSTANCE.removePlayer(player); 72 | } 73 | 74 | // if (DebugDataProvider.INSTANCE.isEnabled()) 75 | // { 76 | // DebugDataProvider.INSTANCE.unregister(player); 77 | // } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/servux/ServerListener.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.servux; 2 | 3 | import net.minecraft.resource.ResourceManager; 4 | import net.minecraft.server.MinecraftServer; 5 | 6 | import fi.dy.masa.servux.dataproviders.DataProviderManager; 7 | import fi.dy.masa.servux.dataproviders.HudDataProvider; 8 | import fi.dy.masa.servux.dataproviders.ServuxConfigProvider; 9 | import fi.dy.masa.servux.interfaces.IServerListener; 10 | import fi.dy.masa.servux.util.i18nLang; 11 | 12 | public class ServerListener implements IServerListener 13 | { 14 | @Override 15 | public void onServerStarting(MinecraftServer server) 16 | { 17 | DataProviderManager.INSTANCE.readFromConfig(); 18 | } 19 | 20 | @Override 21 | public void onServerStarted(MinecraftServer server) 22 | { 23 | DataProviderManager.INSTANCE.writeToConfig(); 24 | DataProviderManager.INSTANCE.onCaptureImmutable(server.getRegistryManager()); 25 | 26 | if (HudDataProvider.INSTANCE.isEnabled()) 27 | { 28 | HudDataProvider.INSTANCE.checkWorldSeed(server); 29 | } 30 | } 31 | 32 | @Override 33 | public void onServerResourceReloadPre(MinecraftServer server, ResourceManager resourceManager) 34 | { 35 | DataProviderManager.INSTANCE.readFromConfig(); 36 | } 37 | 38 | @Override 39 | public void onServerResourceReloadPost(MinecraftServer server, ResourceManager resourceManager, boolean success) 40 | { 41 | DataProviderManager.INSTANCE.writeToConfig(); 42 | DataProviderManager.INSTANCE.onCaptureImmutable(server.getRegistryManager()); 43 | i18nLang.tryLoadLanguage(ServuxConfigProvider.INSTANCE.getDefaultLanguage()); 44 | } 45 | 46 | @Override 47 | public void onServerStopping(MinecraftServer server) 48 | { 49 | DataProviderManager.INSTANCE.onServerTickEndPre(); 50 | DataProviderManager.INSTANCE.writeToConfig(); 51 | } 52 | 53 | @Override 54 | public void onServerStopped(MinecraftServer server) 55 | { 56 | DataProviderManager.INSTANCE.onServerTickEndPost(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/servux/ServuxInitHandler.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.servux; 2 | 3 | import fi.dy.masa.servux.dataproviders.*; 4 | import fi.dy.masa.servux.event.PlayerHandler; 5 | import fi.dy.masa.servux.event.ServerHandler; 6 | import fi.dy.masa.servux.interfaces.IServerInitHandler; 7 | 8 | public class ServuxInitHandler implements IServerInitHandler 9 | { 10 | @Override 11 | public void onServerInit() 12 | { 13 | DataProviderManager.INSTANCE.registerDataProvider(ServuxConfigProvider.INSTANCE); 14 | DataProviderManager.INSTANCE.registerDataProvider(StructureDataProvider.INSTANCE); 15 | DataProviderManager.INSTANCE.registerDataProvider(HudDataProvider.INSTANCE); 16 | DataProviderManager.INSTANCE.registerDataProvider(LitematicsDataProvider.INSTANCE); 17 | DataProviderManager.INSTANCE.registerDataProvider(EntitiesDataProvider.INSTANCE); 18 | DataProviderManager.INSTANCE.registerDataProvider(TweaksDataProvider.INSTANCE); 19 | // DataProviderManager.INSTANCE.registerDataProvider(DebugDataProvider.INSTANCE); 20 | 21 | ServerHandler.getInstance().registerServerHandler(new ServerListener()); 22 | PlayerHandler.getInstance().registerPlayerHandler(new PlayerListener()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/AbstractServuxSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 4 | import fi.dy.masa.servux.dataproviders.IDataProvider; 5 | import fi.dy.masa.servux.util.i18nLang; 6 | import net.minecraft.text.Text; 7 | 8 | import javax.annotation.Nullable; 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | public abstract class AbstractServuxSetting implements IServuxSetting 13 | { 14 | private final String name; 15 | private final Text prettyName; 16 | private final Text comment; 17 | private final T defaultValue; 18 | private final List examples; 19 | private final IDataProvider dataProvider; 20 | private final @Nullable IServuxSettingCallback callback; 21 | 22 | public AbstractServuxSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, T defaultValue, List examples, IServuxSettingCallback callback) 23 | { 24 | Objects.requireNonNull(name); 25 | this.name = name; 26 | this.prettyName = prettyName; 27 | this.comment = comment; 28 | this.defaultValue = defaultValue; 29 | this.value = defaultValue; 30 | this.examples = examples; 31 | this.dataProvider = dataProvider; 32 | this.callback = callback; 33 | } 34 | 35 | public AbstractServuxSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, T defaultValue, IServuxSettingCallback callback) 36 | { 37 | this(dataProvider, name, prettyName, comment, defaultValue, null, callback); 38 | } 39 | 40 | public AbstractServuxSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, T defaultValue) 41 | { 42 | this(dataProvider, name, prettyName, comment, defaultValue, null, null); 43 | } 44 | 45 | private T value; 46 | 47 | @Override 48 | public T getDefaultValue() 49 | { 50 | return defaultValue; 51 | } 52 | 53 | @Override 54 | public T getValue() 55 | { 56 | return value; 57 | } 58 | 59 | @Override 60 | /** 61 | * the value field should not be modified directly, please invoke this method. 62 | * override this value to handle all the value changes, even caused by reading config. 63 | */ 64 | public void setValueNoCallback(T value) 65 | { 66 | this.value = value; 67 | } 68 | 69 | @Override 70 | public void setValue(T value) throws CommandSyntaxException 71 | { 72 | var oldValue = this.getValue(); 73 | setValueNoCallback(value); 74 | onValueChanged(oldValue, value); 75 | } 76 | 77 | @Override 78 | public IDataProvider dataProvider() 79 | { 80 | return dataProvider; 81 | } 82 | 83 | protected void onValueChanged(T oldValue, T value) 84 | { 85 | if (this.callback != null) 86 | { 87 | this.callback.onValueChanged(this, oldValue, value); 88 | } 89 | } 90 | 91 | @Override 92 | public void setValueFromString(String value) throws CommandSyntaxException 93 | { 94 | if (this.validateString(value)) 95 | { 96 | setValue(this.valueFromString(value)); 97 | } 98 | } 99 | 100 | @Override 101 | public String name() 102 | { 103 | return name; 104 | } 105 | 106 | @Override 107 | public Text prettyName() 108 | { 109 | if (prettyName == null) 110 | { 111 | return i18nLang.getInstance().translate("servux.config."+dataProvider.getName()+"."+name+".name"); 112 | } 113 | return prettyName; 114 | } 115 | 116 | @Override 117 | public Text comment() 118 | { 119 | if (comment == null) 120 | { 121 | return i18nLang.getInstance().translate("servux.config."+dataProvider.getName()+"."+name+".comment"); 122 | } 123 | return comment; 124 | } 125 | 126 | @Override 127 | public List examples() 128 | { 129 | if (examples == null) 130 | { 131 | return List.of(); 132 | } 133 | return examples; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/IServuxSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import fi.dy.masa.servux.dataproviders.IDataProvider; 6 | import net.minecraft.text.HoverEvent; 7 | import net.minecraft.text.Text; 8 | import net.minecraft.util.Formatting; 9 | 10 | import java.util.List; 11 | 12 | public interface IServuxSetting 13 | { 14 | String name(); 15 | 16 | Text prettyName(); 17 | 18 | Text comment(); 19 | 20 | List examples(); 21 | 22 | IDataProvider dataProvider(); 23 | 24 | T getDefaultValue(); 25 | 26 | T getValue(); 27 | 28 | void setValueNoCallback(T value); 29 | 30 | void setValue(T value) throws CommandSyntaxException; 31 | 32 | /** 33 | * Set the value from a string representation, this is used when setting the value from commands 34 | * 35 | * @throws CommandSyntaxException if the value is invalid 36 | */ 37 | void setValueFromString(String value) throws CommandSyntaxException; 38 | 39 | boolean validateString(String value); 40 | 41 | String valueToString(Object value); 42 | 43 | T valueFromString(String value); 44 | 45 | void readFromJson(JsonElement element); 46 | 47 | JsonElement writeToJson(); 48 | 49 | default Text shortDisplayName() 50 | { 51 | return prettyName().copy().styled(style -> 52 | //style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, comment().copy() 53 | //.append(Text.literal("\n(%s)".formatted(qualifiedName())).formatted(Formatting.DARK_GRAY)))) 54 | style.withHoverEvent(new HoverEvent.ShowText(comment().copy() 55 | .append(Text.literal("\n(%s)".formatted(qualifiedName())) 56 | .formatted(Formatting.DARK_GRAY)))) 57 | .withColor(Formatting.YELLOW) 58 | ); 59 | } 60 | 61 | default String qualifiedName() 62 | { 63 | return dataProvider().getName() + ":" + name(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/IServuxSettingCallback.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | public interface IServuxSettingCallback 4 | { 5 | void onValueChanged(IServuxSetting setting, T oldValue, T value); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/ServuxBoolSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | import fi.dy.masa.servux.dataproviders.IDataProvider; 6 | import net.minecraft.text.Text; 7 | 8 | import java.util.List; 9 | 10 | public class ServuxBoolSetting extends AbstractServuxSetting 11 | { 12 | public ServuxBoolSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, boolean defaultValue, IServuxSettingCallback callback) 13 | { 14 | super(dataProvider, name, prettyName, comment, defaultValue, List.of("true", "false"), callback); 15 | } 16 | 17 | public ServuxBoolSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, boolean defaultValue) 18 | { 19 | super(dataProvider, name, prettyName, comment, defaultValue, List.of("true", "false"), null); 20 | } 21 | 22 | public ServuxBoolSetting(IDataProvider dataProvider, String name, boolean defaultValue, IServuxSettingCallback callback) 23 | { 24 | super(dataProvider, name, null, null, defaultValue, List.of("true", "false"), callback); 25 | } 26 | 27 | public ServuxBoolSetting(IDataProvider dataProvider, String name, boolean defaultValue) 28 | { 29 | super(dataProvider, name, null, null, defaultValue, List.of("true", "false"), null); 30 | } 31 | 32 | @Override 33 | public boolean validateString(String value) 34 | { 35 | return value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"); 36 | } 37 | 38 | @Override 39 | public String valueToString(Object value) 40 | { 41 | return ((Boolean) value).toString(); 42 | } 43 | 44 | @Override 45 | public Boolean valueFromString(String value) 46 | { 47 | return Boolean.parseBoolean(value); 48 | } 49 | 50 | @Override 51 | public void readFromJson(JsonElement element) 52 | { 53 | if (element.isJsonPrimitive()) 54 | { 55 | var value = element.getAsJsonPrimitive(); 56 | 57 | if (value.isBoolean()) 58 | { 59 | this.setValueNoCallback(value.getAsBoolean()); 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public JsonElement writeToJson() 66 | { 67 | return new JsonPrimitive(this.getValue()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/ServuxIntSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | import fi.dy.masa.servux.dataproviders.IDataProvider; 6 | import net.minecraft.text.Text; 7 | 8 | public class ServuxIntSetting extends AbstractServuxSetting 9 | { 10 | private final int maxValue; 11 | private final int minValue; 12 | 13 | public ServuxIntSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, int defaultValue, int maxValue, int minValue, IServuxSettingCallback callback) 14 | { 15 | super(dataProvider ,name, prettyName, comment, defaultValue, callback); 16 | this.maxValue = maxValue; 17 | this.minValue = minValue; 18 | } 19 | 20 | public ServuxIntSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, int defaultValue, int maxValue, int minValue) 21 | { 22 | super(dataProvider ,name, prettyName, comment, defaultValue); 23 | this.maxValue = maxValue; 24 | this.minValue = minValue; 25 | } 26 | 27 | public ServuxIntSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, int defaultValue, IServuxSettingCallback callback) 28 | { 29 | this(dataProvider, name, prettyName, comment, defaultValue, Integer.MAX_VALUE, Integer.MIN_VALUE, callback); 30 | } 31 | 32 | public ServuxIntSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, int defaultValue) 33 | { 34 | this(dataProvider, name, prettyName, comment, defaultValue, Integer.MAX_VALUE, Integer.MIN_VALUE); 35 | } 36 | 37 | public ServuxIntSetting(IDataProvider dataProvider, String name, int defaultValue, int maxValue, int minValue, IServuxSettingCallback callback) 38 | { 39 | this(dataProvider, name, null, null, defaultValue, maxValue, minValue, callback); 40 | } 41 | 42 | public ServuxIntSetting(IDataProvider dataProvider, String name, int defaultValue, int maxValue, int minValue) 43 | { 44 | this(dataProvider, name, null, null, defaultValue, maxValue, minValue); 45 | } 46 | 47 | public ServuxIntSetting(IDataProvider dataProvider, String name, int defaultValue, IServuxSettingCallback callback) 48 | { 49 | this(dataProvider, name, null, null, defaultValue, Integer.MAX_VALUE, Integer.MIN_VALUE, callback); 50 | } 51 | 52 | public ServuxIntSetting(IDataProvider dataProvider, String name, int defaultValue) 53 | { 54 | this(dataProvider, name, null, null, defaultValue, Integer.MAX_VALUE, Integer.MIN_VALUE); 55 | } 56 | 57 | @Override 58 | public boolean validateString(String value) 59 | { 60 | try 61 | { 62 | int val = Integer.parseInt(value); 63 | return val >= this.minValue && val <= this.maxValue; 64 | } 65 | catch (NumberFormatException e) 66 | { 67 | return false; 68 | } 69 | } 70 | 71 | @Override 72 | public String valueToString(Object value) 73 | { 74 | return ((Integer) value).toString(); 75 | } 76 | 77 | @Override 78 | public Integer valueFromString(String value) 79 | { 80 | return Integer.parseInt(value); 81 | } 82 | 83 | @Override 84 | public void readFromJson(JsonElement element) 85 | { 86 | if (element.isJsonPrimitive()) 87 | { 88 | var value = element.getAsJsonPrimitive(); 89 | 90 | if (value.isNumber()) 91 | { 92 | this.setValueNoCallback(value.getAsInt()); 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public JsonElement writeToJson() 99 | { 100 | return new JsonPrimitive(this.getValue()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/ServuxListSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import fi.dy.masa.servux.dataproviders.IDataProvider; 7 | import net.minecraft.text.Text; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | public abstract class ServuxListSetting extends AbstractServuxSetting> 13 | { 14 | private static final Gson GSON = new Gson(); 15 | 16 | public ServuxListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, List examples, IServuxSettingCallback> callback) 17 | { 18 | super(dataProvider, name, prettyName, comment, defaultValue, examples, callback); 19 | } 20 | 21 | // public ServuxListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, List examples, String separatorRegex) 22 | // { 23 | // this(dataProvider, name, prettyName, comment, defaultValue, examples); 24 | // } 25 | 26 | public ServuxListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, List examples) 27 | { 28 | super(dataProvider, name, prettyName, comment, defaultValue, examples, null); 29 | } 30 | 31 | @Override 32 | public boolean validateString(String value) 33 | { 34 | JsonArray array = GSON.fromJson(value, JsonArray.class); 35 | for (JsonElement element : array) 36 | { 37 | if (!this.validateJsonForElement(element)) 38 | { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | 45 | public abstract boolean validateJsonForElement(JsonElement value); 46 | 47 | @SuppressWarnings("unchecked") 48 | @Override 49 | public String valueToString(Object value) 50 | { 51 | JsonArray array = new JsonArray(); 52 | for (T ele : (List) value) 53 | { 54 | array.add(this.writeElementToJson(ele)); 55 | } 56 | return GSON.toJson(array); 57 | } 58 | 59 | @Override 60 | public List valueFromString(String value) 61 | { 62 | return GSON.fromJson(value, JsonArray.class).asList().stream().map(this::readElementFromJson).toList(); 63 | } 64 | 65 | @Override 66 | public void readFromJson(JsonElement element) 67 | { 68 | if (element.isJsonArray()) 69 | { 70 | var array = element.getAsJsonArray(); 71 | var list = array.asList().stream().map(this::readElementFromJson).collect(Collectors.toList()); 72 | this.setValueNoCallback(list); 73 | } 74 | } 75 | 76 | public abstract T readElementFromJson(JsonElement element); 77 | 78 | @Override 79 | public JsonElement writeToJson() 80 | { 81 | JsonArray array = new JsonArray(); 82 | for (T value : this.getValue()) 83 | { 84 | array.add(this.writeElementToJson(value)); 85 | } 86 | return array; 87 | } 88 | 89 | public abstract JsonElement writeElementToJson(T value); 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/ServuxStringListSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | import fi.dy.masa.servux.dataproviders.IDataProvider; 6 | import net.minecraft.text.Text; 7 | 8 | import java.util.List; 9 | 10 | public class ServuxStringListSetting extends ServuxListSetting 11 | { 12 | public ServuxStringListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, List examples, IServuxSettingCallback> callback) 13 | { 14 | super(dataProvider, name, prettyName, comment, defaultValue, examples, callback); 15 | } 16 | 17 | public ServuxStringListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, List examples) 18 | { 19 | super(dataProvider, name, prettyName, comment, defaultValue, examples); 20 | } 21 | 22 | public ServuxStringListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue, IServuxSettingCallback> callback) 23 | { 24 | super(dataProvider, name, prettyName, comment, defaultValue, List.of(), callback); 25 | } 26 | 27 | public ServuxStringListSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, List defaultValue) 28 | { 29 | super(dataProvider, name, prettyName, comment, defaultValue, List.of()); 30 | } 31 | 32 | public ServuxStringListSetting(IDataProvider dataProvider, String name, List defaultValue, IServuxSettingCallback> callback) 33 | { 34 | super(dataProvider, name, null, null, defaultValue, List.of(), callback); 35 | } 36 | 37 | public ServuxStringListSetting(IDataProvider dataProvider, String name, List defaultValue) 38 | { 39 | super(dataProvider, name, null, null, defaultValue, List.of()); 40 | } 41 | 42 | @Override 43 | public boolean validateJsonForElement(JsonElement value) 44 | { 45 | return value instanceof JsonPrimitive primitive && primitive.isString(); 46 | } 47 | 48 | @Override 49 | public String readElementFromJson(JsonElement element) 50 | { 51 | return element.getAsString(); 52 | } 53 | 54 | @Override 55 | public JsonElement writeElementToJson(String value) 56 | { 57 | return new JsonPrimitive(value); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/settings/ServuxStringSetting.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.settings; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | import fi.dy.masa.servux.dataproviders.IDataProvider; 6 | import net.minecraft.text.Text; 7 | 8 | import java.util.List; 9 | 10 | public class ServuxStringSetting extends AbstractServuxSetting 11 | { 12 | private final boolean strict; 13 | public ServuxStringSetting(IDataProvider dataProvider, String name, String defaultValue, List examples, boolean strict, IServuxSettingCallback callback) 14 | { 15 | this(dataProvider, name, null, null, defaultValue, examples, strict, callback); 16 | } 17 | 18 | public ServuxStringSetting(IDataProvider dataProvider, String name, String defaultValue, List examples, boolean strict) 19 | { 20 | this(dataProvider, name, null, null, defaultValue, examples, strict); 21 | } 22 | 23 | public ServuxStringSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, String defaultValue, List examples, boolean strict, IServuxSettingCallback callback) 24 | { 25 | super(dataProvider, name, prettyName, comment, defaultValue, examples, callback); 26 | this.strict = strict; 27 | } 28 | 29 | public ServuxStringSetting(IDataProvider dataProvider, String name, Text prettyName, Text comment, String defaultValue, List examples, boolean strict) 30 | { 31 | super(dataProvider, name, prettyName, comment, defaultValue, examples, null); 32 | this.strict = strict; 33 | } 34 | 35 | @Override 36 | public boolean validateString(String value) 37 | { 38 | return strict ? this.examples().contains(value) : true; 39 | } 40 | 41 | @Override 42 | public String valueToString(Object value) 43 | { 44 | return (String) value; 45 | } 46 | 47 | @Override 48 | public String valueFromString(String value) 49 | { 50 | return value; 51 | } 52 | 53 | @Override 54 | public void readFromJson(JsonElement element) 55 | { 56 | if (element instanceof JsonPrimitive primitive) 57 | { 58 | this.setValueNoCallback(primitive.getAsString()); 59 | } 60 | } 61 | 62 | @Override 63 | public JsonElement writeToJson() 64 | { 65 | return new JsonPrimitive(this.getValue()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | public class FileUtils 4 | { 5 | public static String getNameWithoutExtension(String name) 6 | { 7 | int i = name.lastIndexOf("."); 8 | return i != -1 ? name.substring(0, i) : name; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/IWorldUpdateSuppressor.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | public interface IWorldUpdateSuppressor 4 | { 5 | boolean servux_getShouldPreventBlockUpdates(); 6 | 7 | void servux_setShouldPreventBlockUpdates(boolean preventUpdates); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/InventoryUtils.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import net.minecraft.block.ShulkerBoxBlock; 4 | import net.minecraft.component.DataComponentTypes; 5 | import net.minecraft.component.type.ContainerComponent; 6 | import net.minecraft.item.BlockItem; 7 | import net.minecraft.item.ItemStack; 8 | 9 | public class InventoryUtils 10 | { 11 | public static boolean isShulkerBox(ItemStack stack) 12 | { 13 | return stack.getItem() instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock; 14 | } 15 | 16 | public static boolean shulkerBoxHasItems(ItemStack stack) 17 | { 18 | ContainerComponent container = stack.getComponents().get(DataComponentTypes.CONTAINER); 19 | 20 | if (container != null) 21 | { 22 | return container.iterateNonEmpty().iterator().hasNext(); 23 | } 24 | 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/LayerMode.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import java.util.function.IntFunction; 4 | import com.google.common.collect.ImmutableList; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | import net.minecraft.network.codec.PacketCodec; 8 | import net.minecraft.network.codec.PacketCodecs; 9 | import net.minecraft.util.StringIdentifiable; 10 | import net.minecraft.util.function.ValueLists; 11 | 12 | public enum LayerMode implements StringIdentifiable 13 | { 14 | ALL (0, "all", "malilib.gui.label.layer_mode.all"), 15 | SINGLE_LAYER (1, "single_layer", "malilib.gui.label.layer_mode.single_layer"), 16 | LAYER_RANGE (2, "layer_range", "malilib.gui.label.layer_mode.layer_range"), 17 | ALL_BELOW (3, "all_below", "malilib.gui.label.layer_mode.all_below"), 18 | ALL_ABOVE (4, "all_above", "malilib.gui.label.layer_mode.all_above"); 19 | 20 | public static final EnumCodec CODEC = StringIdentifiable.createCodec(LayerMode::values); 21 | public static final IntFunction INDEX_TO_VALUE = ValueLists.createIndexToValueFunction(LayerMode::getIndex, values(), ValueLists.OutOfBoundsHandling.WRAP); 22 | public static final PacketCodec PACKET_CODEC = PacketCodecs.indexed(INDEX_TO_VALUE, LayerMode::getIndex); 23 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 24 | 25 | private final int index; 26 | private final String configString; 27 | private final String translationKey; 28 | 29 | LayerMode(int index, String configString, String translationKey) 30 | { 31 | this.index = index; 32 | this.configString = configString; 33 | this.translationKey = translationKey; 34 | } 35 | 36 | public int getIndex() 37 | { 38 | return this.index; 39 | } 40 | 41 | @Override 42 | public String asString() 43 | { 44 | return this.configString; 45 | } 46 | 47 | public static LayerMode fromStringStatic(String name) 48 | { 49 | for (LayerMode mode : VALUES) 50 | { 51 | if (mode.configString.equalsIgnoreCase(name)) 52 | { 53 | return mode; 54 | } 55 | } 56 | 57 | return LayerMode.ALL; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/PasteLayerBehavior.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import net.minecraft.util.StringIdentifiable; 6 | 7 | public enum PasteLayerBehavior implements StringIdentifiable 8 | { 9 | ALL ("all", "litematica.gui.label.paste_layer_behavior.all"), 10 | RENDERED_ONLY ("rendered_only", "litematica.gui.label.paste_layer_behavior.rendered_only"); 11 | 12 | public static final EnumCodec CODEC = StringIdentifiable.createCodec(PasteLayerBehavior::values); 13 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 14 | private final String configString; 15 | private final String translationKey; 16 | 17 | PasteLayerBehavior(String configString, String translationKey) 18 | { 19 | this.configString = configString; 20 | this.translationKey = translationKey; 21 | } 22 | 23 | @Override 24 | public String asString() 25 | { 26 | return this.configString; 27 | } 28 | 29 | public static PasteLayerBehavior fromStringStatic(String name) 30 | { 31 | for (PasteLayerBehavior val : PasteLayerBehavior.values()) 32 | { 33 | if (val.configString.equalsIgnoreCase(name)) 34 | { 35 | return val; 36 | } 37 | } 38 | 39 | return PasteLayerBehavior.ALL; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/PlayerDimensionPosition.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import net.minecraft.entity.player.PlayerEntity; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.world.dimension.DimensionType; 6 | 7 | public class PlayerDimensionPosition 8 | { 9 | protected DimensionType dimensionType; 10 | protected BlockPos pos; 11 | 12 | public PlayerDimensionPosition(PlayerEntity player) 13 | { 14 | this.setPosition(player); 15 | } 16 | 17 | public boolean dimensionChanged(PlayerEntity player) 18 | { 19 | return this.dimensionType != player.getEntityWorld().getDimension(); 20 | } 21 | 22 | public boolean needsUpdate(PlayerEntity player, int distanceThreshold) 23 | { 24 | if (player.getEntityWorld().getDimension() != this.dimensionType) 25 | { 26 | return true; 27 | } 28 | 29 | BlockPos pos = player.getBlockPos(); 30 | 31 | return Math.abs(pos.getX() - this.pos.getX()) > distanceThreshold || 32 | Math.abs(pos.getY() - this.pos.getY()) > distanceThreshold || 33 | Math.abs(pos.getZ() - this.pos.getZ()) > distanceThreshold; 34 | } 35 | 36 | public void setPosition(PlayerEntity player) 37 | { 38 | this.dimensionType = player.getEntityWorld().getDimension(); 39 | this.pos = player.getBlockPos(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/ReplaceBehavior.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import net.minecraft.util.StringIdentifiable; 6 | 7 | public enum ReplaceBehavior implements StringIdentifiable 8 | { 9 | NONE ("none", "litematica.gui.label.replace_behavior.none"), 10 | ALL ("all", "litematica.gui.label.replace_behavior.all"), 11 | WITH_NON_AIR ("with_non_air", "litematica.gui.label.replace_behavior.with_non_air"); 12 | 13 | public static final StringIdentifiable.EnumCodec CODEC = StringIdentifiable.createCodec(ReplaceBehavior::values); 14 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 15 | private final String configString; 16 | private final String translationKey; 17 | 18 | ReplaceBehavior(String configString, String translationKey) 19 | { 20 | this.configString = configString; 21 | this.translationKey = translationKey; 22 | } 23 | 24 | public static ReplaceBehavior fromStringStatic(String name) 25 | { 26 | for (ReplaceBehavior val : VALUES) 27 | { 28 | if (val.configString.equalsIgnoreCase(name)) 29 | { 30 | return val; 31 | } 32 | } 33 | 34 | return ReplaceBehavior.NONE; 35 | } 36 | 37 | @Override 38 | public String asString() 39 | { 40 | return this.configString; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 4 | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; 5 | import net.minecraft.text.MutableText; 6 | import net.minecraft.util.Identifier; 7 | 8 | public class StringUtils 9 | { 10 | public static String getModVersionString(String modId) 11 | { 12 | for (net.fabricmc.loader.api.ModContainer container : net.fabricmc.loader.api.FabricLoader.getInstance().getAllMods()) 13 | { 14 | if (container.getMetadata().getId().equals(modId)) 15 | { 16 | return container.getMetadata().getVersion().getFriendlyString(); 17 | } 18 | } 19 | 20 | return "?"; 21 | } 22 | 23 | public static String removeDefaultMinecraftNamespace(Identifier settingId) 24 | { 25 | return settingId.getNamespace().equals("minecraft") ? settingId.getPath() : settingId.toString(); 26 | } 27 | 28 | /** 29 | * Can replace I18n 30 | * @param translationKey (key) 31 | * @param args (...args) 32 | */ 33 | public static MutableText translate(String translationKey, Object... args) 34 | { 35 | return i18nLang.getInstance().translate(translationKey, args); 36 | } 37 | 38 | public static CommandSyntaxException translateError(String translationKey, Object... args) 39 | { 40 | return new SimpleCommandExceptionType(translate(translationKey, args)).create(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/Timeout.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | public class Timeout 4 | { 5 | protected int lastSync; 6 | 7 | public Timeout(int currentTick) 8 | { 9 | this.lastSync = currentTick; 10 | } 11 | 12 | public boolean needsUpdate(int currentTick, int timeout) 13 | { 14 | return currentTick - this.lastSync >= timeout; 15 | } 16 | 17 | public void setLastSync(int tickCounter) 18 | { 19 | this.lastSync = tickCounter; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/WorldUtils.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import net.minecraft.world.World; 4 | 5 | public class WorldUtils 6 | { 7 | public static boolean shouldPreventBlockUpdates(World world) 8 | { 9 | return ((IWorldUpdateSuppressor) world).servux_getShouldPreventBlockUpdates(); 10 | } 11 | 12 | public static void setShouldPreventBlockUpdates(World world, boolean preventUpdates) 13 | { 14 | ((IWorldUpdateSuppressor) world).servux_setShouldPreventBlockUpdates(preventUpdates); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/data/Constants.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util.data; 2 | 3 | public class Constants 4 | { 5 | public static class NBT 6 | { 7 | public static final int TAG_END = 0; 8 | public static final int TAG_BYTE = 1; 9 | public static final int TAG_SHORT = 2; 10 | public static final int TAG_INT = 3; 11 | public static final int TAG_LONG = 4; 12 | public static final int TAG_FLOAT = 5; 13 | public static final int TAG_DOUBLE = 6; 14 | public static final int TAG_BYTE_ARRAY = 7; 15 | public static final int TAG_STRING = 8; 16 | public static final int TAG_LIST = 9; 17 | public static final int TAG_COMPOUND = 10; 18 | public static final int TAG_INT_ARRAY = 11; 19 | public static final int TAG_LONG_ARRAY = 12; 20 | public static final int TAG_ANY_NUMERIC = 99; 21 | } 22 | 23 | /** 24 | * Replaces removed call to net.minecraft.SharedConstants 25 | */ 26 | public static boolean isValidChar(char chr) 27 | { 28 | return chr != 167 && chr >= ' ' && chr != 127; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/data/FileType.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util.data; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import net.minecraft.util.StringIdentifiable; 8 | 9 | public enum FileType implements StringIdentifiable 10 | { 11 | INVALID, 12 | UNKNOWN, 13 | JSON, 14 | LITEMATICA_SCHEMATIC, 15 | SCHEMATICA_SCHEMATIC, 16 | SPONGE_SCHEMATIC, 17 | VANILLA_STRUCTURE; 18 | 19 | public static final StringIdentifiable.EnumCodec CODEC = StringIdentifiable.createCodec(FileType::values); 20 | public static final ImmutableList VALUES = ImmutableList.copyOf(values()); 21 | 22 | public static FileType fromName(String fileName) 23 | { 24 | if (fileName.endsWith(".litematic")) 25 | { 26 | return LITEMATICA_SCHEMATIC; 27 | } 28 | else if (fileName.endsWith(".schematic")) 29 | { 30 | return SCHEMATICA_SCHEMATIC; 31 | } 32 | else if (fileName.endsWith(".nbt")) 33 | { 34 | return VANILLA_STRUCTURE; 35 | } 36 | else if (fileName.endsWith(".schem")) 37 | { 38 | return SPONGE_SCHEMATIC; 39 | } 40 | else if (fileName.endsWith(".json")) 41 | { 42 | return JSON; 43 | } 44 | 45 | return UNKNOWN; 46 | } 47 | 48 | public static FileType fromFile(Path file) 49 | { 50 | if (Files.exists(file) && Files.isReadable(file)) 51 | { 52 | return fromName(file.getFileName().toString()); 53 | } 54 | else 55 | { 56 | return INVALID; 57 | } 58 | } 59 | 60 | public static String getFileExt(FileType type) 61 | { 62 | return switch (type) 63 | { 64 | case LITEMATICA_SCHEMATIC -> ".litematic"; 65 | case SCHEMATICA_SCHEMATIC -> ".schematic"; 66 | case SPONGE_SCHEMATIC -> ".schem"; 67 | case VANILLA_STRUCTURE -> ".nbt"; 68 | case JSON -> ".json"; 69 | case INVALID -> ".invalid"; 70 | case UNKNOWN -> ".unknown"; 71 | }; 72 | } 73 | 74 | public static String getString(FileType type) 75 | { 76 | return switch (type) 77 | { 78 | case LITEMATICA_SCHEMATIC -> "litematic"; 79 | case SCHEMATICA_SCHEMATIC -> "schematic"; 80 | case SPONGE_SCHEMATIC -> "sponge"; 81 | case VANILLA_STRUCTURE -> "vanilla_nbt"; 82 | case JSON -> "JSON"; 83 | case INVALID -> "invalid"; 84 | case UNKNOWN -> "unknown"; 85 | }; 86 | } 87 | 88 | @Override 89 | public String asString() 90 | { 91 | return getString(this); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/data/ResourceLocation.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util.data; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import javax.annotation.Nullable; 6 | import io.netty.buffer.ByteBuf; 7 | 8 | import com.mojang.serialization.Codec; 9 | import com.mojang.serialization.codecs.RecordCodecBuilder; 10 | import net.minecraft.network.codec.PacketCodec; 11 | import net.minecraft.network.codec.PacketCodecs; 12 | import net.minecraft.util.Identifier; 13 | 14 | /** 15 | * Wraps the Mojmap "ResourceLocation" with Identifier 16 | * - 17 | * Post-ReWrite code 18 | */ 19 | public class ResourceLocation 20 | { 21 | public static final Codec CODEC = RecordCodecBuilder.create( 22 | resourceLocationInstance -> resourceLocationInstance.group( 23 | Identifier.CODEC.fieldOf("id").forGetter(get -> get.id) 24 | ).apply(resourceLocationInstance, ResourceLocation::new) 25 | ); 26 | public static final PacketCodec PACKET_CODEC = PacketCodecs.STRING.xmap(ResourceLocation::of, ResourceLocation::toString); 27 | private final Identifier id; 28 | 29 | public ResourceLocation(String str) 30 | { 31 | this.id = Identifier.of(str); 32 | } 33 | 34 | public ResourceLocation(String name, String path) 35 | { 36 | this.id = Identifier.of(name, path); 37 | } 38 | 39 | public ResourceLocation(Identifier id) 40 | { 41 | this.id = id; 42 | } 43 | 44 | public static ResourceLocation of(String str) 45 | { 46 | return new ResourceLocation(str); 47 | } 48 | 49 | public static ResourceLocation of(String name, String path) 50 | { 51 | return new ResourceLocation(name, path); 52 | } 53 | 54 | public static ResourceLocation ofVanilla(String path) 55 | { 56 | return new ResourceLocation("minecraft", path); 57 | } 58 | 59 | public static ResourceLocation of(Identifier id) 60 | { 61 | return new ResourceLocation(id); 62 | } 63 | 64 | public static List of(List list) 65 | { 66 | List newList = new ArrayList<>(); 67 | 68 | list.forEach((id) -> newList.add(ResourceLocation.of(id))); 69 | 70 | return newList; 71 | } 72 | 73 | public @Nullable Identifier getId() 74 | { 75 | return this.id; 76 | } 77 | 78 | public String getNamespace() 79 | { 80 | return this.id.getNamespace(); 81 | } 82 | 83 | public String getPath() 84 | { 85 | return this.id.getPath(); 86 | } 87 | 88 | public String toTranslationKey() 89 | { 90 | return this.id.getNamespace()+"."+this.id.getPath(); 91 | } 92 | 93 | @Override 94 | public String toString() 95 | { 96 | return this.id.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/fi/dy/masa/servux/util/i18nLang.java: -------------------------------------------------------------------------------- 1 | package fi.dy.masa.servux.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Map; 8 | import java.util.function.BiConsumer; 9 | import javax.annotation.Nullable; 10 | import com.google.common.collect.ImmutableMap; 11 | import com.google.gson.Gson; 12 | import com.google.gson.JsonElement; 13 | import com.google.gson.JsonObject; 14 | 15 | import net.minecraft.text.HoverEvent; 16 | import net.minecraft.text.MutableText; 17 | import net.minecraft.text.Text; 18 | import net.minecraft.util.Formatting; 19 | 20 | import fi.dy.masa.servux.Reference; 21 | import fi.dy.masa.servux.Servux; 22 | 23 | public class i18nLang 24 | { 25 | private static final Gson GSON = new Gson(); 26 | public static final String DEFAULT_LANG = "en_us"; 27 | public static final String DEFAULT_PATH = "/assets/"+Reference.MOD_ID+"/lang/"; 28 | private static volatile i18nLang instance = null; 29 | static 30 | { 31 | tryLoadLanguage(DEFAULT_LANG); 32 | } 33 | private final String languageCode; 34 | private final Map map; 35 | 36 | public i18nLang(String languageCode, Map map) 37 | { 38 | this.languageCode = languageCode; 39 | this.map = map; 40 | } 41 | 42 | public static i18nLang create(String languageCode, String path) throws IOException 43 | { 44 | ImmutableMap.Builder builder = ImmutableMap.builder(); 45 | BiConsumer biConsumer = builder::put; 46 | load(biConsumer, path); 47 | final Map map = builder.build(); 48 | 49 | return new i18nLang(languageCode, map); 50 | } 51 | 52 | public static i18nLang create(String languageCode) throws IOException 53 | { 54 | return create(languageCode, DEFAULT_PATH + languageCode + ".json"); 55 | } 56 | 57 | public static void load(BiConsumer entryConsumer, String path) throws IOException 58 | { 59 | InputStream inputStream = i18nLang.class.getResourceAsStream(path); 60 | 61 | try 62 | { 63 | if (inputStream != null) 64 | { 65 | JsonObject jsonObject = GSON.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class); 66 | 67 | for (Map.Entry entry : jsonObject.entrySet()) 68 | { 69 | entryConsumer.accept(entry.getKey(), entry.getValue().getAsString()); 70 | } 71 | } 72 | else 73 | { 74 | throw new IOException("Couldn't find the file: " + path); 75 | } 76 | } 77 | catch (Throwable var6) 78 | { 79 | if (inputStream != null) 80 | { 81 | try 82 | { 83 | inputStream.close(); 84 | } 85 | catch (Throwable var5) 86 | { 87 | var6.addSuppressed(var5); 88 | } 89 | } 90 | 91 | throw var6; 92 | } 93 | 94 | inputStream.close(); 95 | } 96 | 97 | public static i18nLang getInstance() 98 | { 99 | return instance; 100 | } 101 | 102 | public static void setInstance(i18nLang language) 103 | { 104 | instance = language; 105 | } 106 | 107 | public static boolean tryLoadLanguage(String langCode) 108 | { 109 | try 110 | { 111 | instance = create(langCode); 112 | return true; 113 | } 114 | catch (Exception e) 115 | { 116 | Servux.LOGGER.error("Failed to load language file for '{}'", langCode, e); 117 | return false; 118 | } 119 | } 120 | 121 | public @Nullable String get(String key) 122 | { 123 | return map.get(key); 124 | } 125 | 126 | public MutableText translate(String key, Object... args) 127 | { 128 | if (hasTranslation(key)) 129 | { 130 | return Text.translatableWithFallback(key, get(key), args); 131 | } 132 | else 133 | { 134 | return Text.literal(key).styled((style) -> 135 | style.withColor(Formatting.RED) 136 | //.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.of("Missing translation: "+key)))); 137 | .withHoverEvent(new HoverEvent.ShowText(Text.of("Missing translation: "+key)))); 138 | } 139 | } 140 | 141 | public boolean hasTranslation(String key) 142 | { 143 | return map.containsKey(key); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "servux", 4 | "name": "Servux", 5 | "version": "${mod_version}", 6 | 7 | "description": "Server-side support and integration for masa's client mods", 8 | "license": "LGPLv3", 9 | "authors": [ 10 | "masa" 11 | ], 12 | "contact": { 13 | "homepage": "https://www.curseforge.com/minecraft/mc-mods/servux", 14 | "sources": "https://github.com/maruohon/servux", 15 | "twitter": "https://twitter.com/maruohon", 16 | "discord": "https://discordapp.com/channels/211786369951989762/453662800460644354/" 17 | }, 18 | 19 | "environment": "server", 20 | 21 | "entrypoints": { 22 | "main": [ 23 | "fi.dy.masa.servux.Servux" 24 | ] 25 | }, 26 | 27 | "mixins": [ 28 | "mixins.servux.json" 29 | ], 30 | 31 | "depends": { 32 | "minecraft": ">=1.21.9 <=1.21.10", 33 | "fabric-networking-api-v1": ">=5.0.12" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/mixins.servux.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "fi.dy.masa.servux.mixin", 4 | "compatibilityLevel": "JAVA_21", 5 | "minVersion": "0.8", 6 | "mixins": [ 7 | "MixinMain", 8 | "block.MixinBlock_UpdateSuppression", 9 | "block.MixinChestBlock", 10 | "block.MixinHopperBlockEntity", 11 | "block.MixinRailBlocks", 12 | "block.MixinStairsBlock", 13 | "debug.IMixinMobEntity", 14 | "debug.MixinPath", 15 | "debug.MixinSharedConstants", 16 | "entity.MixinAllayEntity", 17 | "entity.MixinItemEntity", 18 | "entity.MixinMobEntity", 19 | "item.MixinBlockItem_EasyPlace", 20 | "item.MixinItemStack", 21 | "nbt.IMixinNbtReadView", 22 | "nbt.IMixinNbtWriteView", 23 | "network.MixinServerPlayNetworkHandler_EasyPlace", 24 | "network.MixinServerPlayNetworkHandler_QueryNbt", 25 | "server.IMixinServerTickManager", 26 | "server.MixinCommandManager", 27 | "server.MixinMinecraftDedicatedServer", 28 | "server.MixinMinecraftServer", 29 | "server.MixinPlayerManager", 30 | "world.IMixinWorldTickScheduler", 31 | "world.MixinServerChunkLoadingManager", 32 | "world.MixinServerWorld", 33 | "world.MixinWorld_UpdateSuppression", 34 | "world.MixinWorldChunk_UpdateSuppression" 35 | ], 36 | "injectors": { 37 | "defaultRequire": 1 38 | } 39 | } 40 | --------------------------------------------------------------------------------