├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── crash_report.yml │ └── feature_request.yml └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── TODO.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ply-anti-xray ├── README.md ├── asm.properties ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── plymouth │ │ └── antixray │ │ ├── Constants.java │ │ ├── LazyChunkManager.java │ │ ├── ShadowBlockView.java │ │ ├── ShadowChunk.java │ │ ├── mixins │ │ ├── player │ │ │ └── MixinPlayerEntity.java │ │ └── world │ │ │ ├── AccessorPalettedContainer.java │ │ │ ├── MixinAlternateCurrentWorldHelper.java │ │ │ ├── MixinBlockView.java │ │ │ ├── MixinServerChunkManager.java │ │ │ ├── MixinServerWorld.java │ │ │ ├── MixinWorld.java │ │ │ └── MixinWorldChunk.java │ │ └── transformers │ │ ├── GudAsmTransformer.java │ │ ├── PacketTransformer.java │ │ ├── StackMut.java │ │ ├── Stub.java │ │ ├── Transformers.java │ │ └── package-info.java │ └── resources │ ├── asm │ └── PacketTargets.sys │ ├── data │ └── plymouth-anti-xray │ │ └── tags │ │ └── blocks │ │ ├── common_structure_blocks.json │ │ ├── containers.json │ │ ├── furnaces.json │ │ ├── hidden.json │ │ ├── operator_blocks.json │ │ ├── ores.json │ │ ├── pistons.json │ │ ├── precious_blocks.json │ │ ├── redstone.json │ │ ├── redstone_containers.json │ │ ├── redstone_non_full.json │ │ └── workstations.json │ ├── fabric.mod.json │ ├── pack.mcmeta │ ├── pack.png │ ├── plymouth-anti-xray.accesswidener │ └── plymouth-anti-xray.mixin.json ├── ply-common ├── README.md ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── plymouth │ │ └── common │ │ ├── InjectableInteractionManager.java │ │ ├── InteractionManagerInjection.java │ │ ├── UUIDHelper.java │ │ └── mixins │ │ └── MixinServerPlayerInteractionManager.java │ └── resources │ ├── fabric.mod.json │ └── plymouth-common.mixin.json ├── ply-database ├── README.md ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── plymouth │ │ └── database │ │ ├── BlockAction.java │ │ ├── DatabaseHelper.java │ │ ├── ItemStackHasher.java │ │ ├── Plymouth.java │ │ ├── PlymouthException.java │ │ ├── PlymouthNoOP.java │ │ ├── PlymouthPostgres.java │ │ ├── PlymouthSQL.java │ │ ├── Target.java │ │ ├── TextUtils.java │ │ └── records │ │ ├── BlockLookupRecord.java │ │ ├── BlockRecord.java │ │ ├── CompletableRecord.java │ │ ├── DeathLookupRecord.java │ │ ├── DeathRecord.java │ │ ├── IntegerPositionRecord.java │ │ ├── InventoryLookupRecord.java │ │ ├── InventoryRecord.java │ │ ├── LookupRecord.java │ │ ├── PlymouthRecord.java │ │ ├── RecordType.java │ │ └── TargetRecord.java │ └── resources │ ├── assets │ └── plymouth-database │ │ └── lang │ │ └── en_us.json │ ├── data │ └── plymouth-database │ │ └── lang │ │ └── en_us.json │ ├── fabric.mod.json │ └── pack.mcmeta ├── ply-debug ├── README.md ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── plymouth │ │ └── debug │ │ ├── Debug.java │ │ ├── DebugClient.java │ │ ├── DebugProfiler.java │ │ ├── Fusebox.java │ │ ├── MixinConfig.java │ │ ├── RenderBatch.java │ │ ├── anti_xray │ │ ├── AntiXrayClientDebugger.java │ │ └── AntiXrayDebugger.java │ │ ├── database │ │ └── PlymouthLoggingDelegate.java.exclude │ │ ├── misc │ │ ├── BoundingBoxDebugClient.java │ │ ├── MiscDebugClient.java │ │ └── ReloadDebugClient.java │ │ └── mixins │ │ ├── client │ │ ├── MixinBustAntiXrayClientPlayNetworkHandler.java │ │ └── MixinBustLoadingMinecraftClient.java │ │ ├── database │ │ └── MixinDatabaseHelper.java.exclude │ │ └── tracker │ │ ├── MixinExplosion.java │ │ └── MixinSlot.java.exclude │ └── resources │ ├── fabric.mod.json │ └── plymouth-debug.mixin.json ├── ply-locking ├── README.md ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── plymouth │ │ └── locking │ │ ├── ILockable.java │ │ ├── LockCommand.java │ │ ├── LockDelegate.java │ │ ├── Locking.java │ │ ├── handler │ │ ├── AdvancedPermissionHandler.java │ │ ├── BasicPermissionHandler.java │ │ ├── IAdvancedPermissionHandler.java │ │ └── IPermissionHandler.java │ │ └── mixins │ │ ├── MixinBlockEntity.java │ │ ├── MixinExplosionBehavior.java │ │ ├── MixinServerPlayerInteractionManager.java │ │ ├── MixinServerWorld.java │ │ ├── MixinWorld.java │ │ └── entities │ │ ├── MixinEnderDragon.java │ │ ├── MixinEntity.java │ │ ├── MixinPlayerEntity.java │ │ └── MixinTntMinecartEntity.java │ └── resources │ ├── assets │ └── plymouth-locking │ │ └── lang │ │ └── en_us.json │ ├── data │ └── plymouth-database │ │ └── lang │ │ └── en_us.json │ ├── fabric.mod.json │ ├── pack.mcmeta │ └── plymouth-locking.mixin.json ├── ply-tracker ├── README.md ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── gay │ │ │ └── ampflower │ │ │ └── plymouth │ │ │ └── tracker │ │ │ ├── Tracker.java │ │ │ ├── TrackerCommand.java │ │ │ ├── TrackerInspectionManagerInjection.java │ │ │ ├── glue │ │ │ ├── ITargetInjectable.java │ │ │ └── RespawnAnchorExplosionBehavior.java │ │ │ └── mixins │ │ │ ├── AccessorDoubleInventory.java │ │ │ ├── MixinAbstractDecorationEntity.java │ │ │ ├── MixinAnvilScreenHandler.java │ │ │ ├── MixinArmorStandEntity.java │ │ │ ├── MixinBlockItem.java │ │ │ ├── MixinExplosion.java │ │ │ ├── MixinFireCharge.java │ │ │ ├── MixinFlintAndSteel.java │ │ │ ├── MixinHybridLPEntity.java │ │ │ ├── MixinPlayerEntity.java │ │ │ ├── MixinPlayerInteractionManager.java │ │ │ ├── ScreenHandlerMixin.java │ │ │ ├── explosions │ │ │ ├── MixinBedBlock.java │ │ │ ├── MixinEndCrystalEntity.java │ │ │ ├── MixinFireballEntity.java │ │ │ └── MixinRespawnAnchorBlock.java │ │ │ └── targets │ │ │ ├── MixinBlockEntity.java │ │ │ ├── MixinDamageSource.java │ │ │ ├── MixinEnderInventory.java │ │ │ ├── MixinEntity.java │ │ │ ├── MixinPlayerEntity.java │ │ │ └── MixinPlayerInventory.java │ └── resources │ │ ├── assets │ │ └── plymouth-tracker │ │ │ └── lang │ │ │ └── en_us.json │ │ ├── data │ │ └── plymouth-database │ │ │ └── lang │ │ │ └── en_us.json │ │ ├── fabric.mod.json │ │ ├── pack.mcmeta │ │ └── plymouth-tracker.mixins.json │ └── test │ └── resources │ └── setup.psql ├── ply-utilities ├── build.gradle.kts └── src │ └── main │ ├── java │ └── gay │ │ └── ampflower │ │ └── helium │ │ ├── Helium.java │ │ ├── commands │ │ ├── HotspotCommand.java │ │ ├── InventoryLookupCommand.java │ │ └── MappingCommand.java │ │ └── mixins │ │ ├── AccessorMapState.java │ │ ├── AccessorPlayerEntity.java │ │ └── MixinCommandManager.java │ └── resources │ ├── assets │ └── plymouth-miscellaneous │ │ └── lang │ │ └── en_us.json │ ├── data │ └── plymouth-miscellaneous │ │ └── lang │ │ └── en_us.json │ ├── fabric.mod.json │ ├── helium.mixins.json │ ├── pack.mcmeta │ └── pack.png ├── settings.gradle.kts └── updater.properties /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report to help us improve. 3 | labels: [ 'bug' ] 4 | 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: What module(s) is this issue related to? 9 | description: If you don't know, select all that were installed. 10 | options: 11 | - label: Anti-Xray `ply-anti-xray` 12 | - label: Common `ply-common` 13 | - label: Database `ply-database` 14 | - label: Locking `ply-locking` 15 | - label: Tracker `ply-tracker` 16 | - label: Miscallenious `plymouth` 17 | - label: Debug `ply-debug` 18 | 19 | - type: textarea 20 | attributes: 21 | label: Environment 22 | description: | 23 | What platform, side and version of Minecraft, Plymouth and Java are you using? 24 | 25 | Example: 26 | - **Minecraft**: 1.17 Server 27 | - **Plymouth**: 0.0.0-beta.5 28 | - **Loader**: Fabric 29 | - **Java**: AdoptOpenJDK 16.0.1 30 | value: | 31 | - **Minecraft**: 32 | - **Plymouth**: 33 | - **Mod Loader**: 34 | - **Java**: 35 | validations: 36 | required: true 37 | 38 | - type: textarea 39 | attributes: 40 | label: To reproduce 41 | description: Steps to reproduce the behaviour. 42 | placeholder: | 43 | 1. ... 44 | 2. ... 45 | validations: 46 | required: true 47 | 48 | - type: input 49 | attributes: 50 | label: Expected results 51 | description: What behaviour did you expect? 52 | placeholder: e.g. To be wither immune. 53 | validations: 54 | required: true 55 | 56 | - type: input 57 | attributes: 58 | label: Actual results 59 | description: What behaviour did you observe instead? 60 | placeholder: e.g. The wither destroyed it. 61 | validations: 62 | required: true 63 | 64 | - type: textarea 65 | attributes: 66 | label: Anything else? 67 | description: Crash reports, server logs, screenshots and videos should go here. 68 | validations: 69 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crash_report.yml: -------------------------------------------------------------------------------- 1 | name: Crash report 2 | description: Submit a crash report to help us improve. 3 | labels: [ 'bug', 'critical' ] 4 | 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Environment 9 | description: | 10 | What platform, side and version of Minecraft, Plymouth and Java are you using? 11 | 12 | Example: 13 | - **Minecraft**: 1.17 Server 14 | - **Plymouth**: 0.0.0-beta.5 15 | - **Loader**: Fabric 16 | - **Java**: AdoptOpenJDK 16.0.1 17 | value: | 18 | - **Minecraft**: 19 | - **Plymouth**: 20 | - **Mod Loader**: 21 | - **Java**: 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | attributes: 27 | label: To reproduce 28 | description: Steps to reproduce the behaviour. 29 | placeholder: | 30 | 1. ... 31 | 2. ... 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | attributes: 37 | label: Crash logs 38 | description: | 39 | The crash report. 40 | 41 | Tip: You can click the text box then drag the files into here in place of the details tags. 42 | value: | 43 |
Latest logs 44 | 45 | 46 | 47 |
48 |
Crash Report 49 | 50 | 51 | 52 |
53 | validations: 54 | required: true 55 | 56 | - type: textarea 57 | attributes: 58 | label: Anything else? 59 | description: Any additional logs, screenshots and videos should go here. 60 | validations: 61 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project. 3 | labels: [ 'enhancement' ] 4 | 5 | body: 6 | - type: input 7 | attributes: 8 | label: Module to add this to? 9 | description: The module you'd like to see this added to. 10 | placeholder: e.g. Tracker 11 | validations: 12 | required: false 13 | 14 | - type: textarea 15 | attributes: 16 | label: What would you like to be added? 17 | placeholder: A clear and concise description of what you want to happen. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: Alternative solutions 24 | placeholder: A clear description of any alternatives you've considered. 25 | validations: 26 | required: false 27 | 28 | - type: textarea 29 | attributes: 30 | label: Additional context? 31 | validations: 32 | required: false -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Gradle 2 | 3 | on: 4 | push: 5 | tags-ignore: 6 | - '**' 7 | branches: 8 | - '**' 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 15 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 1 20 | submodules: true 21 | - uses: gradle/wrapper-validation-action@v1 22 | - uses: actions/cache@v3 23 | with: 24 | path: | 25 | ~/.gradle/caches 26 | ~/.gradle/wrapper 27 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 28 | restore-keys: | 29 | ${{ runner.os }}-gradle- 30 | - uses: actions/setup-java@v3 31 | with: 32 | distribution: temurin 33 | java-version: 17 34 | - name: Build with Gradle 35 | run: ./gradlew build poolBuilds --no-daemon 36 | - name: Upload build artifacts 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: build-artifacts 40 | path: | 41 | build/pool/ -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 15 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 1 16 | submodules: true 17 | - uses: gradle/wrapper-validation-action@v1 18 | - uses: actions/cache@v3 19 | with: 20 | path: | 21 | ~/.gradle/caches 22 | ~/.gradle/wrapper 23 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 24 | restore-keys: | 25 | ${{ runner.os }}-gradle- 26 | - uses: actions/setup-java@v3 27 | with: 28 | distribution: temurin 29 | java-version: 17 30 | - name: Build and publish with Gradle 31 | run: ./gradlew build publish poolBuilds --no-daemon 32 | env: 33 | BUILD_RELEASE: ${{github.event.prelease == false}} 34 | MODRINTH_TOKEN: ${{secrets.MODRINTH}} 35 | CHANGELOG: ${{ github.event.release.body }} 36 | - name: Upload build artifacts 37 | uses: AButler/upload-release-assets@v2.0 38 | with: 39 | files: 'build/pool/*' 40 | repo-token: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | run/ 2 | .gradle/ 3 | .idea/ 4 | build/ 5 | remappedSrc/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "utilities"] 2 | path = utilities 3 | url = git@github.com:Ampflower/hachimitsu-utilities.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plymouth Utilities 2 |
3 | 4 | # Plymouth 5 | 6 | [![Java CI with Gradle](https://github.com/Modflower/plymouth-fabric/actions/workflows/build.yml/badge.svg)](https://github.com/Modflower/plymouth-fabric/actions/workflows/build.yml) 7 | [![License](https://img.shields.io/github/license/Modflower/plymouth-fabric)](LICENSE) 8 |
9 | [![Stable](https://img.shields.io/github/v/release/Modflower/plymouth-fabric?label=stable)](https://github.com/Modflower/plymouth-fabric/releases) 10 | [![Beta](https://img.shields.io/github/v/release/Modflower/plymouth-fabric?include_prereleases&label=beta)](https://github.com/Modflower/plymouth-fabric/releases) 11 |
12 | [![Discord](https://img.shields.io/discord/380201541078089738?color=7289da&label=Development&logo=discord&logoColor=7289da)](https://discord.gg/EmPS9y9) 13 | [![Discord](https://img.shields.io/discord/368932049354227712?color=7289da&label=Community&logo=discord&logoColor=7289da)](https://discord.gg/ExCdXwP) 14 | 15 | An anti-xray engine, claiming system and world tracker. 16 | 17 | ## [Plymouth: Anti-Xray](ply-anti-xray/README.md) 18 | 19 | A cache-based anti-xray engine for FabricMC that replaces ores, buried chests and other materials with surrounding 20 | materials. 21 | 22 | ## [Plymouth: Common](ply-common/README.md) 23 | 24 | Internal utilities for deriving UUIDs from various objects and temporarily injecting into the player interaction 25 | manager. 26 | 27 | ## [Plymouth: Database](ply-database/README.md) 28 | 29 | Database API for Tracker. Requires extra setup due to the use of PostgreSQL. Please 30 | see [the setup guide](ply-database/README.md#setup-postgresql--linux) for how to setup the database. 31 | 32 | ## [Plymouth: Debug](ply-debug/README.md) 33 | 34 | Debugging mod for the anti-xray engine. Not intended for production use. If installed on the server, any client with the 35 | debug client can see any recently hidden blocks otherwise hidden by the anti-xray engine. 36 | 37 | ## [Plymouth: Locking](ply-locking/README.md) 38 | 39 | Chest... well, any block entity claiming and locking mod that allows players to lock various block entities and have 40 | them only be mutable by them unless they choose to open it up for others. 41 | 42 | ## [Plymouth: Tracker](ply-tracker/README.md) 43 | 44 | World, inventory and death tracking system. Can do lookups, but not rollbacks at the moment. 45 | 46 |
-------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Inject into on movement & do 8x4x5 (basically what you're looking at) sending once shadow by lighting has been 2 | implemented. 3 | (Pulled directly from MixinPlayerEntity) 4 | 5 | ```java 6 | @Override 7 | public void setPos(double x, double y, double z) { 8 | double i = prevX, j = prevY, k = prevZ; 9 | super.setPos(x, y, z); 10 | } 11 | ``` -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } 7 | 8 | val minecraft_version: String by project 9 | val yarn_mappings: String by project 10 | val loader_version: String by project 11 | val project_version: String by project 12 | 13 | val isPublish = System.getenv("GITHUB_EVENT_NAME") == "release" 14 | val isRelease = System.getenv("BUILD_RELEASE").toBoolean() 15 | val isActions = System.getenv("GITHUB_ACTIONS").toBoolean() 16 | val baseVersion: String = "$project_version+mc.$minecraft_version" 17 | val globalVersion = when { 18 | isRelease -> baseVersion 19 | isActions -> "$baseVersion-build.${System.getenv("GITHUB_RUN_NUMBER")}-commit.${System.getenv("GITHUB_SHA").substring(0, 7)}-branch.${System.getenv("GITHUB_REF")?.substring(11)?.replace('/', '.') ?: "unknown"}" 20 | else -> "$baseVersion-build.local" 21 | } 22 | 23 | group = "gay.ampflower" 24 | version = globalVersion 25 | 26 | java { 27 | sourceCompatibility = JavaVersion.VERSION_17 28 | targetCompatibility = JavaVersion.VERSION_17 29 | withSourcesJar() 30 | } 31 | 32 | repositories { 33 | mavenCentral() 34 | } 35 | 36 | dependencies { 37 | minecraft("com.mojang", "minecraft", minecraft_version) 38 | mappings("net.fabricmc", "yarn", yarn_mappings, classifier = "v2") 39 | modImplementation("net.fabricmc", "fabric-loader", loader_version) 40 | } 41 | 42 | subprojects { 43 | if (name.startsWith("ply-")) { 44 | apply(plugin = "java") 45 | apply(plugin = "java-library") 46 | apply(plugin = "fabric-loom") 47 | apply(plugin = "maven-publish") 48 | 49 | group = "gay.ampflower" 50 | version = globalVersion 51 | 52 | java { 53 | sourceCompatibility = JavaVersion.VERSION_17 54 | targetCompatibility = JavaVersion.VERSION_17 55 | withSourcesJar() 56 | } 57 | 58 | repositories { 59 | maven("https://maven.nucleoid.xyz") 60 | } 61 | 62 | dependencies { 63 | minecraft("com.mojang", "minecraft", minecraft_version) 64 | mappings("net.fabricmc", "yarn", yarn_mappings, classifier = "v2") 65 | modImplementation("net.fabricmc", "fabric-loader", loader_version) 66 | modImplementation("xyz.nucleoid", "server-translations-api", "2.0.0-beta.2+1.19.4-pre2") 67 | } 68 | 69 | tasks { 70 | withType { 71 | options.encoding = "UTF-8" 72 | options.isDeprecation = true 73 | options.isWarnings = true 74 | } 75 | processResources { 76 | val map = mapOf( 77 | "version" to project.version, 78 | "project_version" to project_version, 79 | "loader_version" to loader_version, 80 | "minecraft_required" to project.property("minecraft_required")?.toString() 81 | ) 82 | inputs.properties(map) 83 | 84 | filesMatching("fabric.mod.json") { 85 | expand(map) 86 | } 87 | } 88 | withType { 89 | from("LICENSE") 90 | } 91 | } 92 | } 93 | } 94 | 95 | tasks { 96 | register("poolBuilds") { 97 | dependsOn(build) 98 | if (isPublish) { 99 | for (p in subprojects) { 100 | if (p.name == "ply-debug" || !p.name.startsWith("ply-")) continue 101 | from(p.tasks.remapJar) 102 | } 103 | } else { 104 | for (p in subprojects) { 105 | if (p.name == "ply-debug" || !p.name.startsWith("ply-")) continue 106 | from(p.tasks.remapJar, p.tasks.remapSourcesJar) 107 | } 108 | } 109 | into(project.buildDir.resolve("pool")) 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Sat Dec 11 14:22:04 CST 2021 2 | minecraft_required=1.19.4 3 | yarn_mappings=1.19.4+build.1 4 | fabric_api_version=0.76.0+1.19.4 5 | jupiter_version=5.+ 6 | fabric_permissions_version=0.1-SNAPSHOT 7 | org.gradle.jvmargs=-Xmx1G 8 | fabric.loom.multiProjectOptimisation=true 9 | systemProp.loom_version=1.+ 10 | systemProp.spotless_version=5.+ 11 | minecraft_version=1.19.4 12 | postgres_version=42.+ 13 | loader_version=0.14.+ 14 | systemProp.minotaur_version=2.+ 15 | project_version=0.2.0 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modflower/plymouth-fabric/fe0987dac6e90b0c379d5ff93afd06a53fa0a49a/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-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ply-anti-xray/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Anti-Xray 2 |
3 | 4 | # Plymouth: Anti-Xray 5 | A cache-based anti-xray engine designed for FabricMC that by default hides precious vanilla materials such as ores and 6 | buried chests. 7 | 8 | This mod is not intended to go on the client. The option is however there if you like doing LAN parties with people you 9 | don't trust with your materials, or if you don't trust yourself to not hack the game, even though Plymouth cannot 10 | entirely prevent hacks whilst running alongside with said hacks on the same instance of Minecraft. 11 | 12 | ## Downloads 13 | You may download Anti-Xray from [Modrinth](https://modrinth.com/mod/plymouth-anti-xray) or from 14 | [GitHub Releases](https://github.com/Modflower/plymouth-fabric/releases). 15 | 16 | ## Usage 17 | Drop the mod into the mods folder of your server then boot it up. No configuring nor commands required. 18 | 19 | Do note, unusually flat bedrock from x-ray view is to be expected, as bedrock is considered to be a hidden block 20 | via `#operator_blocks`. 21 | 22 | ## Configuration 23 | Anti-Xray can be configured by datapacks. There are two resource tags you'll need to cover, which are 24 | `plymouth-anti-xray:hidden` and `plymouth-anti-xray:no_smear`. 25 | 26 | All tags that will work includes... 27 | 28 | - `hidden` - All blocks that the anti-xray engine will attempt to hide. 29 | - `no_smear` - All blocks that the anti-xray engine will not 'smear' across the map to hide hidden blocks. Typically 30 | non-full blocks or multiblocks that would be obviously out of place, such as plants or doors. 31 | - `common_structure_blocks` - Various building blocks. 32 | - `containers` - Various container types. 33 | - `furnaces` - Various furnace types. 34 | - `infested` - Note, infested blocks are hardcoded within the engine to always be replaced with their regular 35 | counterpart. There is no override available. 36 | - `operator_blocks` - Command blocks and structure blocks. 37 | - `ores` - Stores the ores themselves, including the reference to `#gold_ores`. 38 | - `pistons` - Intended to store both the regular piston and sticky piston. 39 | - `precious_blocks` - Stores the ores, and the 10 crafted blocks. 40 | - `redstone` - Various redstone components end up here. 41 | - `redstone_containers` - Redstone components that happen to be containers, such as hoppers and droppers. 42 | - `redstone_non_full` - Redstone components that don't take up a full block of space. 43 | - `workstations` - Places villagers work at. 44 | 45 | If you wish to replace `hidden` or `no_smear`, you can make a datapack as you normally would, and at 46 | `data/plymouth-anti-xray/tags/blocks/hidden.json`, (or `no_smear.json`), you can add in the following: 47 | 48 | ```json 49 | { 50 | "$comment": "'replace' when set to true will replace every other instance of this tag. Use sparingly.", 51 | "replace": true, 52 | "values": [ 53 | "#plymouth-anti-xray:precious_blocks", 54 | "#plymouth-anti-xray:redstone", 55 | "#plymouth-anti-xray:operator_blocks", 56 | "#plymouth-anti-xray:common_structure_blocks", 57 | "dragon_egg" 58 | ] 59 | } 60 | ``` 61 | 62 | For mod makers, always set `replace` to `false` to avoid overriding any configurations that maybe added in later. 63 | 64 |
-------------------------------------------------------------------------------- /ply-anti-xray/asm.properties: -------------------------------------------------------------------------------- 1 | # Mappings of Transformer -> Package 2 | PacketTransformer=net/minecraft/network/packet/s2c/play/ -------------------------------------------------------------------------------- /ply-anti-xray/gradle.properties: -------------------------------------------------------------------------------- 1 | #Sat Dec 11 14:22:04 CST 2021 2 | modrinth_id=6Zrbdphe 3 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/Constants.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.registry.Registries; 5 | import net.minecraft.registry.tag.TagKey; 6 | import net.minecraft.util.Identifier; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * @author Ampflower 12 | * @since 0.0.0 13 | **/ 14 | public final class Constants { 15 | public static final Logger LOGGER = LoggerFactory.getLogger("Plymouth Anti-Xray"); 16 | 17 | /** 18 | * @deprecated Will be replaced by a different configuration scheme later on. 19 | */ 20 | @Deprecated 21 | public static final TagKey HIDDEN_BLOCKS = TagKey.of(Registries.BLOCK.getKey(), new Identifier("plymouth-anti-xray", "hidden")); 22 | 23 | private Constants() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/LazyChunkManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray; 2 | 3 | import net.minecraft.world.chunk.Chunk; 4 | 5 | /** 6 | * ChunkManager interface for more lazily getting loaded chunks when possible. 7 | * 8 | * @author Ampflower 9 | * @since ${version} 10 | **/ 11 | public interface LazyChunkManager { 12 | /** 13 | * Fetches the chunk in varying states of loaded. 14 | *

15 | * This is primarily intended to work around Concurrent Chunk Management Engine 16 | * sending and loading chunks mid-tick in a way where it can stall the server until 17 | * either the player intervenes in the case of single player, or 18 | * {@link net.minecraft.server.dedicated.DedicatedServerWatchdog Watchdog} or the 19 | * sysadmin intervenes in the case of a dedicated server. 20 | * 21 | * @param chunkX X coordinate of chunk to fetch. 22 | * @param chunkZ Z coordinate of chunk to fetch. 23 | * @return Chunk of varying state of initialised. 24 | */ 25 | Chunk plymouth$getChunkLazy(int chunkX, int chunkZ); 26 | } 27 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/ShadowBlockView.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.block.entity.BlockEntity; 5 | import net.minecraft.util.math.BlockPos; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | /** 10 | * @author Ampflower 11 | * @since ${version} 12 | **/ 13 | public interface ShadowBlockView { 14 | /** 15 | * Gets the block from the shadow chunk, if the shadow is present. 16 | * 17 | * @param pos The position of the block. 18 | * @return The block at that position, {@link net.minecraft.block.Blocks#VOID_AIR} otherwise. 19 | */ 20 | @NotNull 21 | BlockState plymouth$getShadowBlock(BlockPos pos); 22 | 23 | /** 24 | * Gets the block entity from the shadow chunk, if the shadow is present. 25 | * 26 | * @param pos The position of the block. 27 | * @return The block entity at that position if it exists and not hidden, null otherwise. 28 | */ 29 | @Nullable BlockEntity plymouth$getShadowBlockEntity(BlockPos pos); 30 | } 31 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/player/MixinPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.player; 2 | 3 | import gay.ampflower.plymouth.antixray.ShadowChunk; 4 | import net.minecraft.block.Blocks; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.util.math.Direction; 9 | import net.minecraft.world.World; 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.CallbackInfo; 14 | 15 | /** 16 | * You know, I really shouldn't have to, but people seem to really like abusing composters. 17 | * 18 | * @author Ampflower 19 | * @since 0.0.0 20 | **/ 21 | @Mixin(PlayerEntity.class) 22 | public abstract class MixinPlayerEntity extends LivingEntity { 23 | protected MixinPlayerEntity(EntityType entityType, World world) { 24 | super(entityType, world); 25 | } 26 | 27 | @Inject(method = "updatePose()V", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/EntityPose;SWIMMING:Lnet/minecraft/entity/EntityPose;", ordinal = 2)) 28 | private void helium$onUpdateSize(CallbackInfo ci) { 29 | // We need access to shadow immediately to force shadow replacement with spruce planks. 30 | var self = getBlockPos(); 31 | var chunk = (ShadowChunk) world.getChunk(self); 32 | if (chunk.plymouth$isMasked(self)) return; 33 | var possibleComposter = chunk.plymouth$getShadowBlock(self); 34 | if (possibleComposter.isOf(Blocks.COMPOSTER)) { 35 | var abovePosition = self.up(); 36 | var aboveBlock = chunk.plymouth$getShadowBlock(abovePosition); 37 | if (chunk.plymouth$isCulling(aboveBlock, Direction.DOWN, abovePosition)) { 38 | chunk.plymouth$maskBlock(self, Blocks.SPRUCE_PLANKS.getDefaultState()); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/AccessorPalettedContainer.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import net.minecraft.util.collection.IndexedIterable; 4 | import net.minecraft.world.chunk.PalettedContainer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since ${version} 11 | **/ 12 | @Mixin(PalettedContainer.class) 13 | public interface AccessorPalettedContainer { 14 | @Accessor 15 | IndexedIterable getIdList(); 16 | 17 | @Accessor 18 | PalettedContainer.PaletteProvider getPaletteProvider(); 19 | 20 | @Accessor 21 | PalettedContainer.Data getData(); 22 | } 23 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/MixinAlternateCurrentWorldHelper.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import gay.ampflower.plymouth.antixray.ShadowChunk; 4 | import net.minecraft.server.world.ServerChunkManager; 5 | import net.minecraft.util.math.BlockPos; 6 | import net.minecraft.util.math.ChunkSectionPos; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Pseudo; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | /** 13 | * Provides a compatibility layer for Alternate Current's LevelHelper. 14 | * 15 | * @author Ampflower 16 | * @since ${version} 17 | **/ 18 | @Pseudo 19 | @Mixin(targets = "alternate.current.wire.LevelHelper") 20 | public class MixinAlternateCurrentWorldHelper { 21 | /** 22 | * Updates the underlying shadow mask if present. 23 | *

24 | * If present and not hidden, continues to mark the block for update as normal. 25 | * 26 | * @param self The ServerChunkManager being redirected. 27 | * @param pos The position being marked for update. 28 | */ 29 | @Redirect(method = "setWireState(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)Z", 30 | at = @At(value = "INVOKE", 31 | target = "Lnet/minecraft/server/world/ServerChunkManager;markForUpdate(Lnet/minecraft/util/math/BlockPos;)V")) 32 | private static void plymouth$onMarkForUpdate(ServerChunkManager self, BlockPos pos) { 33 | int x = ChunkSectionPos.getSectionCoord(pos.getX()); 34 | int z = ChunkSectionPos.getSectionCoord(pos.getZ()); 35 | var c = (ShadowChunk) self.getWorldChunk(x, z); 36 | if (c != null && c.plymouth$unsafe$uncheckedUpdate(pos)) { 37 | self.markForUpdate(pos); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/MixinBlockView.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import gay.ampflower.plymouth.antixray.ShadowBlockView; 4 | import gay.ampflower.plymouth.antixray.transformers.GudAsmTransformer; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.block.entity.BlockEntity; 7 | import net.minecraft.util.math.BlockPos; 8 | import net.minecraft.world.BlockView; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | 14 | /** 15 | * Forcefully implements the ShadowBlockView interface with default methods. 16 | * 17 | * @author Ampflower 18 | * @since ${version} 19 | **/ 20 | @Mixin(value = BlockView.class, priority = Integer.MAX_VALUE) 21 | public interface MixinBlockView extends ShadowBlockView { 22 | @Shadow 23 | @Nullable BlockEntity getBlockEntity(BlockPos pos); 24 | 25 | @Shadow 26 | BlockState getBlockState(BlockPos pos); 27 | 28 | /** 29 | * Redirector stub for {@link GudAsmTransformer}. 30 | * 31 | * @param pos The position to lookup in the shadow chunk. 32 | * @return The shadow block if applicable, else the standard view. 33 | */ 34 | // This doesn't need any interface stub as it'll be called directly from asm. 35 | default @NotNull BlockState plymouth$getShadowBlock(BlockPos pos) { 36 | return getBlockState(pos); 37 | } 38 | 39 | /** 40 | * Redirector stub for {@link GudAsmTransformer}. 41 | * 42 | * @param pos The position to lookup in the shadow chunk. 43 | * @return The block entity if both existing and visible if applicable, else null. 44 | */ 45 | // This doesn't need any interface stub as it'll be called directly from asm. 46 | default @Nullable BlockEntity plymouth$getShadowBlockEntity(BlockPos pos) { 47 | return getBlockEntity(pos); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/MixinServerChunkManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import gay.ampflower.plymouth.antixray.Constants; 4 | import gay.ampflower.plymouth.antixray.LazyChunkManager; 5 | import net.minecraft.server.world.ChunkHolder; 6 | import net.minecraft.server.world.ServerChunkManager; 7 | import net.minecraft.util.math.ChunkPos; 8 | import net.minecraft.world.chunk.Chunk; 9 | import net.minecraft.world.chunk.ChunkManager; 10 | import net.minecraft.world.chunk.ChunkStatus; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | 17 | /** 18 | * Mixins a lazy implementation of getChunk meant for environments where the 19 | * old engine wasn't suited for. 20 | * 21 | * @author Ampflower 22 | * @since ${version} 23 | **/ 24 | @Mixin(ServerChunkManager.class) 25 | public abstract class MixinServerChunkManager extends ChunkManager implements LazyChunkManager { 26 | 27 | 28 | @Shadow 29 | @Nullable 30 | protected abstract ChunkHolder getChunkHolder(long pos); 31 | 32 | @Shadow 33 | @Final 34 | private Chunk[] chunkCache; 35 | 36 | @Shadow 37 | public abstract @Nullable Chunk getChunk(int x, int z, ChunkStatus leastStatus, boolean create); 38 | 39 | @Shadow 40 | @Final 41 | private long[] chunkPosCache; 42 | 43 | @Unique 44 | private final long[] lazyChunkPosCache = new long[4]; 45 | @Unique 46 | private final Chunk[] lazyChunkCache = new Chunk[4]; 47 | @Unique 48 | private int lazyChunkIndex; 49 | 50 | @Override 51 | public Chunk plymouth$getChunkLazy(int chunkX, int chunkZ) { 52 | long chunkPos = ChunkPos.toLong(chunkX, chunkZ); 53 | 54 | // Prefer the completed cache before falling back to lazy. 55 | for (int i = 0, l = chunkPosCache.length; i < l; i++) { 56 | if (chunkPosCache[i] == chunkPos && chunkCache[i] != null) { 57 | return chunkCache[i]; 58 | } 59 | } 60 | 61 | for (int i = 0, l = lazyChunkPosCache.length; i < l; i++) { 62 | if (lazyChunkPosCache[i] == chunkPos && lazyChunkCache[i] != null) { 63 | return lazyChunkCache[i]; 64 | } 65 | } 66 | 67 | var holder = getChunkHolder(chunkPos); 68 | if (holder == null) { 69 | Constants.LOGGER.warn("Missed holder for {}, {}, falling back to getChunk.", chunkX, chunkZ); 70 | return getChunk(chunkX, chunkZ, ChunkStatus.LIGHT, false); 71 | } 72 | var chunk = holder.getCurrentChunk(); 73 | if (chunk == null) { 74 | Constants.LOGGER.warn("Missed chunk for {}, {}, falling back to getChunk. Got holder {}", chunkX, chunkZ, holder); 75 | return getChunk(chunkX, chunkZ, ChunkStatus.LIGHT, false); 76 | } else potato:{ 77 | int i = lazyChunkIndex++ & 3; 78 | lazyChunkCache[i] = chunk; 79 | lazyChunkPosCache[i] = chunkPos; 80 | for (var section : chunk.getSectionArray()) { 81 | if (!section.isEmpty()) { 82 | break potato; 83 | } 84 | } 85 | Constants.LOGGER.warn("Suspiciously empty chunk @ {}, {}: {}", chunkX, chunkZ, chunk); 86 | } 87 | 88 | return chunk; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/MixinServerWorld.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import gay.ampflower.plymouth.antixray.ShadowChunk; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.registry.DynamicRegistryManager; 6 | import net.minecraft.registry.RegistryKey; 7 | import net.minecraft.registry.entry.RegistryEntry; 8 | import net.minecraft.server.world.ServerChunkManager; 9 | import net.minecraft.server.world.ServerWorld; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.util.profiler.Profiler; 12 | import net.minecraft.world.MutableWorldProperties; 13 | import net.minecraft.world.World; 14 | import net.minecraft.world.dimension.DimensionType; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Redirect; 18 | 19 | import java.util.function.Supplier; 20 | 21 | @Mixin(ServerWorld.class) 22 | public abstract class MixinServerWorld extends World { 23 | 24 | protected MixinServerWorld(MutableWorldProperties properties, RegistryKey registryRef, DynamicRegistryManager registryManager, RegistryEntry dimensionEntry, Supplier profiler, boolean isClient, boolean debugWorld, long biomeAccess, int maxChainedNeighborUpdates) { 25 | super(properties, registryRef, registryManager, dimensionEntry, profiler, isClient, debugWorld, biomeAccess, maxChainedNeighborUpdates); 26 | } 27 | 28 | /** 29 | * Null Router for {@link ServerChunkManager#markForUpdate(BlockPos)} at {@link ServerWorld#updateListeners(BlockPos, BlockState, BlockState, int)} 30 | * 31 | * @reason We're doing our own logic within the shadow chunks. We don't need the world to send 32 | * updates against what the shadow holds. This'll help avoid network overhead in the process 33 | * as the shadow chunks will also suppress needless updates where applicable (ie, shadow is already shadowed). 34 | * We'll call this method ourselves when the mask changes. 35 | * The rest of the method is safe as it doesn't apply to what we are doing. 36 | */ 37 | @Redirect(method = "updateListeners(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V", 38 | at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkManager;markForUpdate(Lnet/minecraft/util/math/BlockPos;)V", ordinal = 0)) 39 | private void helium$updateListeners$nullRoute$markForUpdate(ServerChunkManager self, BlockPos pos, BlockPos $1, BlockState before, BlockState after, int flags) { 40 | // The engine on its own cannot sense block entity updates and will naturally just suppress them. 41 | // This forces an update if it's same-state set, there's a block entity attached, and it's not considered hidden. 42 | if (before == after && after.hasBlockEntity() && !((ShadowChunk) self.getWorldChunk(pos.getX() >> 4, pos.getZ() >> 4, false)).plymouth$isMasked(pos)) { 43 | self.markForUpdate(pos); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/mixins/world/MixinWorld.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.mixins.world; 2 | 3 | import gay.ampflower.plymouth.antixray.ShadowBlockView; 4 | import gay.ampflower.plymouth.antixray.ShadowChunk; 5 | import gay.ampflower.plymouth.antixray.transformers.GudAsmTransformer; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.block.Blocks; 8 | import net.minecraft.block.entity.BlockEntity; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.world.World; 11 | import net.minecraft.world.WorldAccess; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | 16 | /** 17 | * @author Ampflower 18 | * @since ${version} 19 | **/ 20 | @Mixin(World.class) 21 | public abstract class MixinWorld implements WorldAccess, ShadowBlockView { 22 | /** 23 | * Redirector stub for {@link GudAsmTransformer}. 24 | * 25 | * @param pos The position to lookup in the shadow chunk. 26 | * @return The shadow block. 27 | */ 28 | @Override 29 | public @NotNull BlockState plymouth$getShadowBlock(BlockPos pos) { 30 | if (isOutOfHeightLimit(pos)) { 31 | return Blocks.VOID_AIR.getDefaultState(); 32 | } 33 | return ((ShadowChunk) getChunk(pos)).plymouth$getShadowBlock(pos); 34 | } 35 | 36 | /** 37 | * Redirector stub for {@link GudAsmTransformer}. 38 | * 39 | * @param pos The position to lookup in the shadow chunk. 40 | * @return The block entity if both existing and visible, else null. 41 | */ 42 | @Override 43 | public @Nullable BlockEntity plymouth$getShadowBlockEntity(BlockPos pos) { 44 | if (isOutOfHeightLimit(pos)) { 45 | return null; 46 | } 47 | return ((ShadowChunk) getChunk(pos)).plymouth$getShadowBlockEntity(pos); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/transformers/PacketTransformer.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.transformers; 2 | 3 | import net.gudenau.minecraft.asm.api.v1.AsmUtils; 4 | import net.gudenau.minecraft.asm.api.v1.Identifier; 5 | import net.gudenau.minecraft.asm.api.v1.Transformer; 6 | import net.gudenau.minecraft.asm.api.v1.type.MethodType; 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.objectweb.asm.tree.MethodInsnNode; 10 | 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | import static gay.ampflower.plymouth.antixray.transformers.Transformers.logger; 15 | import static gay.ampflower.plymouth.antixray.transformers.Transformers.mkType; 16 | 17 | /** 18 | * Takes every packet defined in {@code asm/PacketTransformer.sys} and transforms 19 | * by the rules of {@link GudAsmTransformer} using {@link Stub} and {@code asm/PacketTargets.sys}. 20 | * 21 | * @author Ampflower 22 | * @since ${version} 23 | **/ 24 | public class PacketTransformer implements Transformer { 25 | private static final Identifier NAME = new Identifier("plymouth-anti-xray", "packet-transformer"); 26 | private final Map invokeVirtualMap; 27 | private final Set classReferences; 28 | 29 | PacketTransformer(Set classReferences, Map invokeVirtualMap) { 30 | this.classReferences = classReferences; 31 | this.invokeVirtualMap = invokeVirtualMap; 32 | } 33 | 34 | @Override 35 | public Identifier getName() { 36 | return NAME; 37 | } 38 | 39 | @Override 40 | public boolean handlesClass(String name, String transformedName) { 41 | return classReferences.remove(name); 42 | } 43 | 44 | @Override 45 | public boolean transform(ClassNode classNode, Flags flags) { 46 | boolean transformed = false; 47 | for (var method : classNode.methods) { 48 | for (var call : AsmUtils.findMatchingNodes(method, n -> n instanceof MethodInsnNode m && 49 | m.getOpcode() != Opcodes.INVOKESTATIC && invokeVirtualMap.containsKey(mkType(m)))) { 50 | if (!(call instanceof MethodInsnNode m1)) { 51 | new AssertionError("Unexpected node " + call).printStackTrace(); 52 | continue; 53 | } 54 | var old = m1.name; 55 | m1.name = invokeVirtualMap.get(mkType(m1)); 56 | logger.info("PAK-AUX: Redirected {}.{}{} in {}.{}{} to {}", m1.owner, old, m1.desc, classNode.name, method.name, method.desc, m1.name); 57 | transformed = true; 58 | } 59 | } 60 | return transformed; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/transformers/StackMut.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.transformers; 2 | 3 | import org.objectweb.asm.tree.AbstractInsnNode; 4 | 5 | /** 6 | * How much to pop and push to the stack. 7 | *

8 | * Meant for instances where the total weight is unsuitable, like 9 | * walking the ASM tree to find usages. 10 | * 11 | * @param pop How much to pop. 12 | * @param push How much to push. 13 | * @author Ampflower 14 | * @see Transformers#stack2(AbstractInsnNode) 15 | * @since ${version} 16 | **/ 17 | public record StackMut(int pop, int push, boolean jmp) { 18 | public StackMut(int pop, int push) { 19 | this(pop, push, false); 20 | } 21 | 22 | static final StackMut 23 | // "he's being hit by a hammer, of course he's surprised" - Deximus-Maximus#0682 24 | T0_0 = new StackMut(0, 0), 25 | T0_1 = new StackMut(0, 1), 26 | T1_0 = new StackMut(1, 0), 27 | T1_1 = new StackMut(1, 1), 28 | T1_2 = new StackMut(1, 2), 29 | T2_0 = new StackMut(2, 0), 30 | T2_1 = new StackMut(2, 1), 31 | T2_2 = new StackMut(2, 2), 32 | T2_3 = new StackMut(2, 3), 33 | T3_0 = new StackMut(3, 0), 34 | J0_0 = new StackMut(0, 0, true), 35 | J0_1 = new StackMut(0, 1, true), 36 | J1_0 = new StackMut(1, 0, true), 37 | J2_0 = new StackMut(2, 0, true), 38 | RET = new StackMut(Integer.MAX_VALUE, 0); 39 | 40 | /** 41 | * Returns the weight calculated by push minus pop. 42 | *

43 | * Unsuitable for use by testing for consumers. 44 | */ 45 | public int weight() { 46 | return push - pop; 47 | // return -pop + push; 48 | } 49 | 50 | /** 51 | * Tests if the instruction will consume the item on stack. 52 | * 53 | * @param stack The current stack size. 54 | * @return If the item would be popped. 55 | */ 56 | public boolean pop(int stack) { 57 | return stack - pop <= 0; 58 | } 59 | 60 | /** 61 | * Tests if the instruction will produce the item on stack. 62 | * 63 | * @param stack The current stack size. 64 | * @return If the item would be pushed. 65 | */ 66 | public boolean push(int stack) { 67 | return stack - push <= 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/transformers/Stub.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.antixray.transformers; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.block.entity.BlockEntity; 5 | import net.minecraft.server.world.ServerWorld; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.world.BlockView; 8 | import net.minecraft.world.World; 9 | import net.minecraft.world.chunk.ChunkSection; 10 | import net.minecraft.world.chunk.WorldChunk; 11 | 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.RetentionPolicy; 14 | import java.util.Map; 15 | 16 | /** 17 | * Provider stub for {@link GudAsmTransformer} to read. 18 | *

19 | * This class is to never be loaded for use. 20 | * 21 | * @author Ampflower 22 | * @since ${version} 23 | * @deprecated Not to be used directly. Deprecation only for warning purposes. 24 | **/ 25 | @Deprecated(forRemoval = true) 26 | class Stub { 27 | // TODO: Automate this with a recursive check for BlockView inheritance. 28 | @MethodNameTo("plymouth$getShadowBlock") 29 | BlockState world(BlockView world, BlockPos pos) { 30 | return world.getBlockState(pos); 31 | } 32 | 33 | @MethodNameTo("plymouth$getShadowBlockEntity") 34 | BlockEntity worldBE(BlockView world, BlockPos pos) { 35 | return world.getBlockEntity(pos); 36 | } 37 | 38 | @MethodNameTo("plymouth$getShadowBlock") 39 | BlockState world(World world, BlockPos pos) { 40 | return world.getBlockState(pos); 41 | } 42 | 43 | @MethodNameTo("plymouth$getShadowBlockEntity") 44 | BlockEntity worldBE(World world, BlockPos pos) { 45 | return world.getBlockEntity(pos); 46 | } 47 | 48 | @MethodNameTo("plymouth$getShadowBlock") 49 | BlockState world(ServerWorld world, BlockPos pos) { 50 | return world.getBlockState(pos); 51 | } 52 | 53 | @MethodNameTo("plymouth$getShadowBlockEntity") 54 | BlockEntity chunkBE(ServerWorld world, BlockPos pos) { 55 | return world.getBlockEntity(pos); 56 | } 57 | 58 | @MethodNameTo("plymouth$getShadowBlock") 59 | BlockState chunk(WorldChunk chunk, BlockPos pos) { 60 | return chunk.getBlockState(pos); 61 | } 62 | 63 | @MethodNameTo("plymouth$getShadowBlockEntity") 64 | BlockEntity chunkBE(WorldChunk chunk, BlockPos pos) { 65 | return chunk.getBlockEntity(pos); 66 | } 67 | 68 | @MethodNameTo("plymouth$getShadowBlockEntities") 69 | Map chunkBEM(WorldChunk chunk) { 70 | return chunk.getBlockEntities(); 71 | } 72 | 73 | @MethodNameTo("plymouth$getShadowSection") 74 | ChunkSection chunkCS(WorldChunk chunk, int y) { 75 | return chunk.getSection(y); 76 | } 77 | 78 | @MethodNameTo("plymouth$getShadowSections") 79 | ChunkSection[] chunkCSA(WorldChunk chunk) { 80 | return chunk.getSectionArray(); 81 | } 82 | 83 | static { 84 | //noinspection ConstantConditions 85 | if (true) throw new AssertionError("This class should never load."); 86 | } 87 | 88 | @Retention(RetentionPolicy.CLASS) 89 | @interface MethodNameTo { 90 | String value(); 91 | } 92 | 93 | @Retention(RetentionPolicy.CLASS) 94 | @interface InjectAtUsage { 95 | String value(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ply-anti-xray/src/main/java/gay/ampflower/plymouth/antixray/transformers/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Transformers implemented using GudASM. 5 | *

6 | * Avoid calling these classes directly, they are not meant for direct use by any mod. 7 | *

8 | * Also avoid calling out into non-transformer-related code, for as that will cascade-load 9 | * classes and ruin any transformations that would otherwise take place here. 10 | * 11 | * @author Ampflower 12 | * @since ${version} 13 | **/ 14 | package gay.ampflower.plymouth.antixray.transformers; -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/asm/PacketTargets.sys: -------------------------------------------------------------------------------- 1 | ;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/world/chunk/ChunkSection;[Lnet/minecraft/world/chunk/ChunkSection; -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/common_structure_blocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#plymouth-anti-xray:workstations", 5 | "chiseled_stone_bricks", 6 | "cracked_stone_bricks", 7 | "mossy_stone_bricks", 8 | "stone_bricks", 9 | "cobblestone", 10 | "mossy_cobblestone", 11 | "#plymouth-anti-xray:containers", 12 | "tnt" 13 | ] 14 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/containers.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#plymouth-anti-xray:furnaces", 5 | "#plymouth-anti-xray:redstone_containers", 6 | "#shulker_boxes", 7 | "#campfires", 8 | "chest", 9 | "ender_chest", 10 | "barrel" 11 | ] 12 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/furnaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": false, 3 | "values": [ 4 | "furnace", 5 | "smoker", 6 | "blast_furnace" 7 | ] 8 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/hidden.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#plymouth-anti-xray:precious_blocks", 5 | "#plymouth-anti-xray:redstone", 6 | "#plymouth-anti-xray:operator_blocks", 7 | "#plymouth-anti-xray:common_structure_blocks", 8 | "dragon_egg", 9 | "spawner", 10 | "lodestone" 11 | ] 12 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/operator_blocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "bedrock", 5 | "barrier", 6 | "command_block", 7 | "repeating_command_block", 8 | "chain_command_block", 9 | "structure_block", 10 | "structure_void", 11 | "jigsaw" 12 | ] 13 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/ores.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#coal_ores", 5 | "#iron_ores", 6 | "#copper_ores", 7 | "#gold_ores", 8 | "#redstone_ores", 9 | "#lapis_ores", 10 | "#emerald_ores", 11 | "#diamond_ores", 12 | "nether_quartz_ore", 13 | "ancient_debris" 14 | ] 15 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/pistons.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "piston", 5 | "sticky_piston", 6 | "piston_head", 7 | "moving_piston" 8 | ] 9 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/precious_blocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#plymouth-anti-xray:ores", 5 | "#beacon_base_blocks", 6 | "raw_iron_block", 7 | "raw_copper_block", 8 | "raw_gold_block", 9 | "coal_block", 10 | "smooth_quartz", 11 | "quartz_block", 12 | "chiseled_quartz_block", 13 | "quartz_pillar", 14 | "quartz_bricks", 15 | "redstone_block", 16 | "lapis_block", 17 | "amethyst_block", 18 | "budding_amethyst", 19 | "amethyst_cluster", 20 | "cut_copper", 21 | "exposed_cut_copper", 22 | "weathered_cut_copper", 23 | "oxidized_cut_copper", 24 | "waxed_cut_copper", 25 | "waxed_exposed_cut_copper", 26 | "waxed_weathered_cut_copper", 27 | "waxed_oxidized_cut_copper", 28 | "copper_block", 29 | "exposed_copper", 30 | "weathered_copper", 31 | "oxidized_copper", 32 | "waxed_copper_block", 33 | "waxed_exposed_copper", 34 | "waxed_weathered_copper", 35 | "waxed_oxidized_copper" 36 | ] 37 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/redstone.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#plymouth-anti-xray:redstone_non_full", 5 | "#plymouth-anti-xray:redstone_containers", 6 | "#plymouth-anti-xray:pistons", 7 | "redstone_block", 8 | "note_block", 9 | "redstone_lamp", 10 | "observer", 11 | "target" 12 | ] 13 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/redstone_containers.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#beehives", 5 | "trapped_chest", 6 | "hopper", 7 | "dropper", 8 | "dispenser" 9 | ] 10 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/redstone_non_full.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "lever", 5 | "#pressure_plates", 6 | "redstone_torch", 7 | "#buttons", 8 | "tripwire_hook", 9 | "tripwire", 10 | "#trapdoors", 11 | "repeater", 12 | "comparator", 13 | "redstone_wire", 14 | "#doors", 15 | "daylight_detector", 16 | "#rails", 17 | "sculk_sensor" 18 | ] 19 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/data/plymouth-anti-xray/tags/blocks/workstations.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | "#anvil", 5 | "#plymouth-anti-xray:furnaces", 6 | "#beds", 7 | "jukebox", 8 | "grindstone", 9 | "crafting_table", 10 | "smithing_table", 11 | "fletching_table", 12 | "cartography_table", 13 | "loom", 14 | "composter", 15 | "stonecutter", 16 | "lectern", 17 | "enchanting_table", 18 | "brewing_stand", 19 | "#cauldrons" 20 | ] 21 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-anti-xray", 4 | "version": "${version}", 5 | "name": "Plymouth: Anti-Xray", 6 | "description": "A cache-based anti-xray engine.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "icon": "pack.png", 11 | "contact": { 12 | "sources": "https://github.com/Modflower/plymouth-fabric", 13 | "discord": "https://discord.gg/EmPS9y9" 14 | }, 15 | "license": [ 16 | "MPL-2.0" 17 | ], 18 | "environment": "*", 19 | "entrypoints": { 20 | "gud_asm": [ 21 | "gay.ampflower.plymouth.antixray.transformers.Transformers" 22 | ] 23 | }, 24 | "mixins": [ 25 | "plymouth-anti-xray.mixin.json" 26 | ], 27 | "accessWidener": "plymouth-anti-xray.accesswidener", 28 | "depends": { 29 | "minecraft": ">=${minecraft_required}", 30 | "fabric-resource-loader-v0": "*", 31 | "gud_asm": ">=0.2.10" 32 | }, 33 | "breaks": { 34 | "gud_asm": "<0.2.10" 35 | } 36 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 7, 4 | "description": "Plymouth: Anti-Xray" 5 | } 6 | } -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/pack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modflower/plymouth-fabric/fe0987dac6e90b0c379d5ff93afd06a53fa0a49a/ply-anti-xray/src/main/resources/pack.png -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/plymouth-anti-xray.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | accessible class net/minecraft/world/chunk/PalettedContainer$Data -------------------------------------------------------------------------------- /ply-anti-xray/src/main/resources/plymouth-anti-xray.mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "gay.ampflower.plymouth.antixray.mixins", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "player.MixinPlayerEntity", 8 | "world.AccessorPalettedContainer", 9 | "world.MixinAlternateCurrentWorldHelper", 10 | "world.MixinBlockView", 11 | "world.MixinServerChunkManager", 12 | "world.MixinServerWorld", 13 | "world.MixinWorld", 14 | "world.MixinWorldChunk" 15 | ], 16 | "injectors": { 17 | "defaultRequire": 1 18 | } 19 | } -------------------------------------------------------------------------------- /ply-common/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Common 2 |

3 | 4 | # Plymouth: Common 5 | 6 | A couple utilities to aid in development for both locking and tracker. 7 | 8 | Includes an injectable interaction manager and a UUID utility class, nothing special here. 9 | 10 | ## Downloads 11 | 12 | You may download Common from [Modrinth](https://modrinth.com/mod/plymouth-common) or 13 | from [GitHub Releases](https://github.com/Modflower/plymouth-fabric/releases). 14 | 15 | ## Usage 16 | 17 | Drop the mod into the mods folder of your server then boot it up. There is no configuration available. 18 | 19 |
-------------------------------------------------------------------------------- /ply-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } -------------------------------------------------------------------------------- /ply-common/src/main/java/gay/ampflower/plymouth/common/InjectableInteractionManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.common; 2 | 3 | /** 4 | * @author Ampflower 5 | * @since 0.0.0 6 | */ 7 | public interface InjectableInteractionManager { 8 | void setManager(InteractionManagerInjection manager); 9 | 10 | InteractionManagerInjection getManager(); 11 | } 12 | -------------------------------------------------------------------------------- /ply-common/src/main/java/gay/ampflower/plymouth/common/InteractionManagerInjection.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.common; 2 | 3 | import net.minecraft.item.ItemStack; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.server.world.ServerWorld; 6 | import net.minecraft.util.ActionResult; 7 | import net.minecraft.util.Hand; 8 | import net.minecraft.util.hit.BlockHitResult; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.util.math.Direction; 11 | 12 | /** 13 | * Callback from the {@link net.minecraft.server.network.ServerPlayerInteractionManager} that intercepts the request 14 | * at the earliest it can, and if consumed, failed or successful, will block the action itself from occurring, 15 | * possibly replacing the blocked action with its own. 16 | * 17 | * @author Ampflower 18 | * @see InjectableInteractionManager#setManager(InteractionManagerInjection) 19 | * @since 0.0.0 20 | */ 21 | public interface InteractionManagerInjection { 22 | /** 23 | * Event for when blocks are being broken. 24 | * 25 | * @param player The player attempting to break a block. 26 | * @param world The world the player's in. 27 | * @param pos The position of the block. 28 | * @param direction The direction the block's getting broken at. 29 | * @return if the action should pass, be consumed, or if it was successful. 30 | */ 31 | ActionResult onBreakBlock(ServerPlayerEntity player, ServerWorld world, BlockPos pos, Direction direction); 32 | 33 | /** 34 | * Event for when blocks are being used. 35 | * 36 | * @param player The player attempting to use a block. 37 | * @param world The world the player's in. 38 | * @param stack The item stack in the player's hand. 39 | * @param hand The hand that activated the request. 40 | * @param hitResult The result of the use. 41 | * @return if the action should pass, be consumed, or if it was successful. 42 | */ 43 | ActionResult onInteractBlock(ServerPlayerEntity player, ServerWorld world, ItemStack stack, Hand hand, BlockHitResult hitResult); 44 | } 45 | -------------------------------------------------------------------------------- /ply-common/src/main/java/gay/ampflower/plymouth/common/mixins/MixinServerPlayerInteractionManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.common.mixins; 2 | 3 | import gay.ampflower.plymouth.common.InjectableInteractionManager; 4 | import gay.ampflower.plymouth.common.InteractionManagerInjection; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; 7 | import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.network.ServerPlayerInteractionManager; 10 | import net.minecraft.server.world.ServerWorld; 11 | import net.minecraft.util.ActionResult; 12 | import net.minecraft.util.Hand; 13 | import net.minecraft.util.hit.BlockHitResult; 14 | import net.minecraft.util.math.BlockPos; 15 | import net.minecraft.util.math.Direction; 16 | import net.minecraft.world.World; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | import org.spongepowered.asm.mixin.Unique; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 24 | 25 | /** 26 | * A manager injector into the interaction manager. Makes perfect sense. 27 | * 28 | * @author Ampflower 29 | * @since 0.0.0 30 | */ 31 | @Mixin(ServerPlayerInteractionManager.class) 32 | public abstract class MixinServerPlayerInteractionManager implements InjectableInteractionManager { 33 | @Shadow 34 | protected ServerPlayerEntity player; 35 | @Shadow 36 | protected ServerWorld world; 37 | 38 | @Shadow 39 | protected abstract void method_41250(BlockPos pos, boolean success, int sequence, String reason); 40 | 41 | @Unique 42 | private InteractionManagerInjection temporaryInjection; 43 | 44 | 45 | @Override 46 | public void setManager(InteractionManagerInjection manager) { 47 | // The following insertion is purely for debugging in case two managers get set at once. This allows easier inspection by simply enabling assertions. 48 | assert temporaryInjection == null; 49 | temporaryInjection = manager; 50 | } 51 | 52 | @Override 53 | public InteractionManagerInjection getManager() { 54 | return temporaryInjection; 55 | } 56 | 57 | @Inject(method = "processBlockBreakingAction", 58 | cancellable = true, 59 | at = @At(value = "HEAD") 60 | ) 61 | private void plymouthCommon$tryBreakBlock(BlockPos pos, PlayerActionC2SPacket.Action action, Direction direction, int worldHeight, int sequence, CallbackInfo ci) { 62 | if (temporaryInjection != null && action == PlayerActionC2SPacket.Action.START_DESTROY_BLOCK) { 63 | var result = temporaryInjection.onBreakBlock(player, world, pos, direction); 64 | if (result != ActionResult.PASS) { 65 | player.networkHandler.sendPacket(new BlockUpdateS2CPacket(pos, this.world.getBlockState(pos))); 66 | method_41250(pos, false, sequence, "intercepted by plymouth"); 67 | ci.cancel(); 68 | } 69 | } 70 | } 71 | 72 | @Inject(method = "interactBlock(Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;", 73 | cancellable = true, 74 | at = @At(value = "HEAD") 75 | ) 76 | private void plymouthCommon$interactBlock(ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult bhr, CallbackInfoReturnable cbir) { 77 | if (temporaryInjection != null) { 78 | var result = temporaryInjection.onInteractBlock(player, (ServerWorld) world, stack, hand, bhr); 79 | if (ActionResult.PASS != result) { 80 | cbir.setReturnValue(result); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ply-common/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-common", 4 | "version": "${version}", 5 | "name": "Plymouth: Common", 6 | "description": "Common API for the Database, Locking and Tracker modules.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/Modflower/plymouth-fabric", 12 | "discord": "https://discord.gg/EmPS9y9" 13 | }, 14 | "environment": "*", 15 | "entrypoints": {}, 16 | "mixins": [ 17 | "plymouth-common.mixin.json" 18 | ], 19 | "depends": { 20 | "minecraft": ">=${minecraft_required}" 21 | } 22 | } -------------------------------------------------------------------------------- /ply-common/src/main/resources/plymouth-common.mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "gay.ampflower.plymouth.common.mixins", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "MixinServerPlayerInteractionManager" 7 | ], 8 | "injectors": { 9 | "defaultRequire": 1 10 | } 11 | } -------------------------------------------------------------------------------- /ply-database/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Database 2 |
3 | 4 | # Plymouth: Database 5 | 6 | A database API for use with Tracker. Currently only supports PostgreSQL. 7 | 8 | ## Downloads 9 | 10 | You may download Database from [Modrinth](https://modrinth.com/mods/plymouth-database) or 11 | from [GitHub Releases](https://github.com/Modflower/plymouth-fabric/releases). 12 | 13 | ## Usage ('Tis rather involved currently due to the database of choice.) 14 | 15 | ### Prerequisites 16 | 17 | - You'll want to be familiar with managing PostgreSQL or similar databases. 18 | - You'll have to be willing to configure the mod initially so it'll work. 19 | - You should be familiar with securing databases to avoid misuse. 20 | 21 | ### Setup (PostgreSQL + Linux) 22 | 23 | 1. Install PostgreSQL using your favourite method. 24 | - [Debian](https://wiki.debian.org/PostgreSql): `apt install postgresql` 25 | - [Fedora/RHEL](https://fedoraproject.org/wiki/PostgreSQL): `dnf install postgresql-server postgresql-contrib` 26 | - Older versions of Redhat Enterprise Linux and derivatives may require the use of `yum` in place of `dnf`. 27 | - This require you to run `sudo postgresql-setup --initdb --unit postgresql` to setup PostgreSQL. 28 | - [Arch Linux](https://wiki.archlinux.org/title/PostgreSQL): `pacman -S postgresql` 29 | - This requires you to run `sudo -u postgres initdb -D /var/lib/postgres/data` 30 | or `su -l postgres -c "initdb -D /var/lib/postgres/data"` to setup PostgreSQL. 31 | - *Some distributions, such as Fedora and Arch Linux may require you to run `systemctl enable --now postgresql` to 32 | start the database and to make it restart on system reboot.* 33 | 2. Create a user and database for Plymouth to use. 34 | - If you prefer to use `psql` directly, you can use the following SQL to get this going. You may need to use 35 | the `postgres` account, which you can access by using `sudo -u postgres psql`. 36 | ```sql 37 | CREATE USER plymouth WITH PASSWORD 'Insert a password for the database here. Be sure to escape your \' as necessary.'; 38 | CREATE DATABASE plymouth WITH OWNER = plymouth; 39 | ``` 40 | - Alternatively, you can use the following two commands. With the `-P` option, you'll be prompted to input a 41 | password. You may need to use the `postgres` account, which you can access by prepending `sudo -u postgres`, or by 42 | going into the account with `su postgres`. 43 | ```sh 44 | createuser plymouth -P 45 | createdb plymouth -O plymouth 46 | ``` 47 | 3. Drop the mod into the mods folder of your server along with Common then boot it up. A configuration file will be 48 | created at `config/plymouth.db.properties` for you to edit. 49 | 4. Edit the config so that the database, username and password matches what you've used for the database. 50 | - Reference 51 | ```properties 52 | url=jdbc\:postgresql\://127.0.0.1\:5432/database 53 | user=username 54 | password=password 55 | ``` 56 | - From the example so far. 57 | ```properties 58 | url=jdbc\:postgresql\://127.0.0.1\:5432/plymouth 59 | user=plymouth 60 | password=Insert a password for the database here. Be sure to escape your ' as necessary. 61 | ``` 62 | 5. Start the server for reals this time. The database handler will bootstrap the database itself with the tables 63 | necessary to function. 64 | 65 |
-------------------------------------------------------------------------------- /ply-database/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } 7 | 8 | val postgres_version: String by project 9 | val fabric_api_version: String by project 10 | 11 | dependencies { 12 | api(project(":ply-common")) 13 | api(project(":database")) { include(this) } 14 | include(implementation("org.postgresql", "postgresql", postgres_version)) 15 | modRuntimeOnly(fabricApi.module("fabric-resource-loader-v0", fabric_api_version)) 16 | } -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/BlockAction.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database; 2 | 3 | import net.minecraft.text.Text; 4 | import net.minecraft.util.Formatting; 5 | 6 | /** 7 | * What actions were taken on the block? 8 | * 9 | * @author Ampflower 10 | * @since ${version} 11 | * @deprecated Will be replaced with old->new when possible. 12 | **/ 13 | @Deprecated 14 | public enum BlockAction { 15 | BREAK(Text.translatable("plymouth.tracker.action.broke").formatted(Formatting.RED)), 16 | PLACE(Text.translatable("plymouth.tracker.action.placed").formatted(Formatting.GREEN)), 17 | USE(Text.translatable("plymouth.tracker.action.used").formatted(Formatting.AQUA)); 18 | 19 | public final Text niceName; 20 | 21 | BlockAction(Text niceName) { 22 | this.niceName = niceName; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/ItemStackHasher.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database; 2 | 3 | import it.unimi.dsi.fastutil.Hash; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.item.ItemStack; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * Custom item stack hasher that only checks the hashcode of the underlying item and NBT, and if the stacks can stack. 11 | * 12 | * @author Ampflower 13 | * @since ${version} 14 | **/ 15 | public final class ItemStackHasher implements Hash.Strategy { 16 | public static final ItemStackHasher INSTANCE = new ItemStackHasher(); 17 | 18 | /** 19 | * @param o The stack to hash. 20 | * @return If o is not null, standard hashcode of ItemStack, else 0. 21 | */ 22 | @Override 23 | public int hashCode(ItemStack o) { 24 | return o == null ? 0 : 31 * o.getItem().hashCode() + Objects.hashCode(o.getNbt()); 25 | } 26 | 27 | /** 28 | * @param i The item to hash. 29 | * @return If i is not null, standard hashcode of Item multiplied by 31 as if it was a stack that had no NBT, else 0. 30 | */ 31 | public static int hashCode(Item i) { 32 | return i == null ? 0 : 31 * i.hashCode(); 33 | } 34 | 35 | /** 36 | * @param a First stack of the equality test. 37 | * @param b Second stack of the equality test. 38 | * @return true only if a is b or if a is not null, b is not null and a can combine into b. 39 | */ 40 | @Override 41 | public boolean equals(ItemStack a, ItemStack b) { 42 | return a == b || a != null && b != null && ItemStack.canCombine(a, b); 43 | } 44 | 45 | /** 46 | * Stack combination test assuming a is a lone item in a stack without NBT. 47 | * 48 | * @param a Item of the equality test. 49 | * @param b Stack of the equality test. 50 | * @return true only if b is not null and if the underlying item in b equals a and b does not have any NBT. 51 | */ 52 | public static boolean equals(Item a, ItemStack b) { 53 | return b != null && a == b.getItem() && !b.hasNbt(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/PlymouthException.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database; 2 | 3 | import java.sql.SQLException; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | 7 | /** 8 | * Exception for database failures or malformed requests. 9 | *

10 | * If {@link SQLException} is passed in any throwable-accepting constructor, 11 | * any following {@link SQLException} contained within the batch update exception 12 | * will be added as suppressed exceptions to allow examining. 13 | * 14 | * @author Ampflower 15 | * @since ${version} 16 | */ 17 | public class PlymouthException extends RuntimeException { 18 | /** 19 | * Exception with only the cause known. No request aliased. 20 | */ 21 | public PlymouthException(Throwable cause) { 22 | super(cause); 23 | if (cause instanceof SQLException sql) { 24 | addBatchedSuppressed(sql); 25 | } 26 | } 27 | 28 | /** 29 | * Exception with the cause known. One request aliased. 30 | */ 31 | public PlymouthException(Throwable cause, Object statement) { 32 | super(Objects.toString(statement), cause); 33 | if (cause instanceof SQLException sql) { 34 | addBatchedSuppressed(sql); 35 | } 36 | } 37 | 38 | /** 39 | * Exception with the cause known. Multiple requests aliased. 40 | */ 41 | public PlymouthException(Throwable cause, Object... statements) { 42 | super(writeOut(statements), cause); 43 | if (cause instanceof SQLException sql) { 44 | addBatchedSuppressed(sql); 45 | } 46 | } 47 | 48 | private static String writeOut(Object[] statements) { 49 | var sb = new StringBuilder("Failure point:\n"); 50 | for (var s : statements) { 51 | sb.append("\n - ").append(s); 52 | } 53 | return sb.toString(); 54 | } 55 | 56 | private void addBatchedSuppressed(SQLException exception) { 57 | var dejavu = new HashSet(); 58 | dejavu.add(exception); 59 | for (var throwable : exception) if (dejavu.add(throwable)) addSuppressed(throwable); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/PlymouthNoOP.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database; 2 | 3 | import gay.ampflower.plymouth.database.records.CompletableRecord; 4 | import gay.ampflower.plymouth.database.records.PlymouthRecord; 5 | import net.minecraft.block.Block; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.entity.LivingEntity; 9 | import net.minecraft.entity.damage.DamageSource; 10 | import net.minecraft.item.Item; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.nbt.NbtCompound; 13 | import net.minecraft.server.world.ServerWorld; 14 | import net.minecraft.util.math.BlockPos; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.UUID; 18 | 19 | /** 20 | * No operation plymouth database driver. Any completable will instantly fail with an unsupported operation exception. 21 | * 22 | * @author Ampflower 23 | * @since ${version} 24 | */ 25 | public class PlymouthNoOP implements Plymouth { 26 | public void initializeDatabase() { 27 | } 28 | 29 | public void sendBatches() { 30 | } 31 | 32 | public void queue(PlymouthRecord record) { 33 | if (record instanceof CompletableRecord completable) { 34 | completable.fail(new UnsupportedOperationException("no-op: record unsupported")); 35 | } 36 | } 37 | 38 | public void breakBlock(ServerWorld world, BlockPos pos, BlockState state, NbtCompound nbt, @Nullable Target cause) { 39 | } 40 | 41 | public void placeBlock(ServerWorld world, BlockPos pos, BlockState state, @Nullable Target cause) { 42 | } 43 | 44 | public void placeBlock(ServerWorld world, BlockPos pos, Block block, @Nullable Target cause) { 45 | } 46 | 47 | public void useBlock(ServerWorld world, BlockPos pos, Item w, @Nullable Target user) { 48 | } 49 | 50 | public void replaceBlock(ServerWorld world, BlockPos pos, BlockState o, BlockState n, @Nullable Target replacer) { 51 | } 52 | 53 | public void killEntity(Target target, Target source) { 54 | } 55 | 56 | public void hurtEntity(LivingEntity target, float amount, DamageSource source) { 57 | } 58 | 59 | public void createEntity(Entity target, Entity creator) { 60 | } 61 | 62 | public void takeItems(Target inventory, ItemStack i, int c, @Nullable Target mutator) { 63 | } 64 | 65 | public void putItems(Target inventory, ItemStack i, int c, @Nullable Target mutator) { 66 | } 67 | 68 | public String getPlayerName(UUID uuid) { 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/Target.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database; 2 | 3 | import gay.ampflower.plymouth.database.records.TargetRecord; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.util.math.Vec3d; 6 | import net.minecraft.world.World; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.Objects; 10 | import java.util.UUID; 11 | 12 | /** 13 | * Target implementation that covers the minimum requirements to be registered within the database. 14 | * 15 | * @author Ampflower 16 | * @since ${version} 17 | **/ 18 | public interface Target { 19 | default TargetRecord plymouth$toRecord() { 20 | return new TargetRecord(ply$world(), ply$pos3i(), ply$pos3d(), ply$name(), ply$userId(), ply$entityId()); 21 | } 22 | 23 | default boolean ply$isBlock() { 24 | return false; 25 | } 26 | 27 | World ply$world(); 28 | 29 | default World ply$blockWorld() { 30 | return ply$isBlock() ? ply$world() : null; 31 | } 32 | 33 | BlockPos ply$pos3i(); 34 | 35 | default BlockPos ply$blockPos3i() { 36 | return ply$isBlock() ? ply$pos3i() : null; 37 | } 38 | 39 | default Vec3d ply$pos3d() { 40 | return Vec3d.ofBottomCenter(ply$pos3i()); 41 | } 42 | 43 | String ply$name(); 44 | 45 | UUID ply$userId(); 46 | 47 | UUID ply$entityId(); 48 | 49 | /** 50 | * An equality method to determine if the targets describes the same entity or block. 51 | * 52 | * @param other The target to test against. 53 | * @return true if other is this or if other is the same user, entity and, if a block, world and position. 54 | */ 55 | default boolean ply$targetMatches(@Nullable Target other) { 56 | return this == other || (other != null && 57 | Objects.equals(ply$userId(), other.ply$userId()) && 58 | Objects.equals(ply$entityId(), other.ply$entityId()) && 59 | ply$isBlock() == other.ply$isBlock() && 60 | (!ply$isBlock() || Objects.equals(ply$pos3i(), other.ply$pos3i()) && Objects.equals(ply$world(), other.ply$world()))); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/BlockLookupRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.server.world.ServerWorld; 5 | import net.minecraft.util.math.BlockPos; 6 | 7 | import java.time.Instant; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Ampflower 13 | * @since ${version} 14 | **/ 15 | public final class BlockLookupRecord extends LookupRecord { 16 | public final ServerWorld targetWorld; 17 | public final BlockPos minTPos, maxTPos; 18 | public final BlockState beforeState, afterState; 19 | 20 | public BlockLookupRecord(ServerWorld world, BlockPos minPosition, BlockPos maxPosition, UUID causeUuid, Instant minTime, Instant maxTime, 21 | ServerWorld targetWorld, BlockPos minTPos, BlockPos maxTPos, BlockState beforeState, BlockState afterState, int page, int flags) { 22 | super(world, minPosition, maxPosition, causeUuid, minTime, maxTime, page, flags); 23 | switch (flags >>> 6 & 3) { 24 | case 0 -> { 25 | this.targetWorld = null; 26 | this.minTPos = null; 27 | this.maxTPos = null; 28 | } 29 | case 1 -> { 30 | this.targetWorld = Objects.requireNonNull(targetWorld, "targetWorld"); 31 | this.minTPos = minTPos.toImmutable(); 32 | this.maxTPos = null; 33 | } 34 | case 2 -> { 35 | this.targetWorld = Objects.requireNonNull(targetWorld, "targetWorld"); 36 | int ax = minTPos.getX(), ay = minTPos.getY(), az = minTPos.getZ(), 37 | bx = maxTPos.getX(), by = maxTPos.getY(), bz = maxTPos.getZ(), 38 | ix = Math.min(ax, bx), iy = Math.min(ay, by), iz = Math.min(az, bz); 39 | if (ax == ix && ay == iy && az == iz) { 40 | this.minTPos = minTPos.toImmutable(); 41 | this.maxTPos = maxTPos.toImmutable(); 42 | } else { 43 | this.minTPos = new BlockPos(ix, iy, iz); 44 | this.maxTPos = new BlockPos(Math.max(ax, bx), Math.max(ay, by), Math.max(az, bz)); 45 | } 46 | } 47 | default -> throw new IllegalStateException("Illegal state 3 on AT & AREA for given flags " + flags); 48 | } 49 | this.beforeState = beforeState; 50 | this.afterState = afterState; 51 | } 52 | 53 | public BlockLookupRecord(ServerWorld world, BlockPos pos, int page) { 54 | this(null, null, null, null, null, null, world, pos, null, null, null, page, FLAG_T_AT); 55 | } 56 | 57 | @Override 58 | public RecordType getType() { 59 | return RecordType.LOOKUP_BLOCK; 60 | } 61 | 62 | @Override 63 | public Class getOutput() { 64 | return BlockRecord.class; 65 | } 66 | 67 | public int minTX() { 68 | return minTPos.getX(); 69 | } 70 | 71 | public int minTY() { 72 | return minTPos.getY(); 73 | } 74 | 75 | public int minTZ() { 76 | return minTPos.getZ(); 77 | } 78 | 79 | public int maxTX() { 80 | return maxTPos.getX(); 81 | } 82 | 83 | public int maxTY() { 84 | return maxTPos.getY(); 85 | } 86 | 87 | public int maxTZ() { 88 | return maxTPos.getZ(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/CompletableRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import java.util.concurrent.CompletionStage; 4 | 5 | /** 6 | * A record that is a request and carries a future that can either be completed or failed. 7 | * 8 | * @author Ampflower 9 | * @since ${version} 10 | **/ 11 | public interface CompletableRecord { 12 | /** 13 | * Polyglot interface for the underlying future. 14 | * 15 | * @param object The object that the request completed with. 16 | */ 17 | void complete(T object); 18 | 19 | /** 20 | * Interface for the underlying future. Implementations of this method must never fail. 21 | * 22 | * @param throwable The error that was thrown when the request failed. 23 | */ 24 | void fail(Throwable throwable); 25 | 26 | /** 27 | * @return The future to hook into. 28 | */ 29 | CompletionStage getFuture(); 30 | } 31 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/DeathLookupRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import net.minecraft.server.world.ServerWorld; 4 | import net.minecraft.util.math.BlockPos; 5 | import net.minecraft.util.math.Vec3d; 6 | 7 | import java.time.Instant; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Ampflower 13 | * @since ${version} 14 | **/ 15 | public final class DeathLookupRecord extends LookupRecord { 16 | public final ServerWorld targetWorld; 17 | public final Vec3d minTPos, maxTPos; 18 | public final UUID targetUserId, targetEntityId; 19 | 20 | public DeathLookupRecord(ServerWorld world, BlockPos minPos, BlockPos maxPos, UUID causeUuid, Instant minTime, Instant maxTime, 21 | ServerWorld targetWorld, Vec3d minTPos, Vec3d maxTPos, UUID targetUserId, UUID targetEntityId, int page, int flags) { 22 | super(world, minPos, maxPos, causeUuid, minTime, maxTime, page, flags); 23 | switch (flags >>> 6 & 3) { 24 | case 0 -> { 25 | this.targetWorld = null; 26 | this.minTPos = null; 27 | this.maxTPos = null; 28 | } 29 | case 1 -> { 30 | this.targetWorld = Objects.requireNonNull(targetWorld, "targetWorld"); 31 | this.minTPos = minTPos; 32 | this.maxTPos = null; 33 | } 34 | case 2 -> { 35 | this.targetWorld = Objects.requireNonNull(targetWorld, "targetWorld"); 36 | double ax = minTPos.getX(), ay = minTPos.getY(), az = minTPos.getZ(), 37 | bx = maxTPos.getX(), by = maxTPos.getY(), bz = maxTPos.getZ(), 38 | ix = Math.min(ax, bx), iy = Math.min(ay, by), iz = Math.min(az, bz); 39 | if (ax == ix && ay == iy && az == iz) { 40 | this.minTPos = minTPos; 41 | this.maxTPos = maxTPos; 42 | } else { 43 | this.minTPos = new Vec3d(ix, iy, iz); 44 | this.maxTPos = new Vec3d(Math.max(ax, bx), Math.max(ay, by), Math.max(az, bz)); 45 | } 46 | } 47 | default -> throw new IllegalStateException("Illegal state 3 on AT & AREA for given flags " + flags); 48 | } 49 | this.targetUserId = targetUserId; 50 | this.targetEntityId = targetEntityId; 51 | } 52 | 53 | @Override 54 | public Class getOutput() { 55 | return DeathRecord.class; 56 | } 57 | 58 | @Override 59 | public RecordType getType() { 60 | return RecordType.LOOKUP_DEATH; 61 | } 62 | 63 | public double minTX() { 64 | return minTPos.getX(); 65 | } 66 | 67 | public double minTY() { 68 | return minTPos.getY(); 69 | } 70 | 71 | public double minTZ() { 72 | return minTPos.getZ(); 73 | } 74 | 75 | public double maxTX() { 76 | return maxTPos.getX(); 77 | } 78 | 79 | public double maxTY() { 80 | return maxTPos.getY(); 81 | } 82 | 83 | public double maxTZ() { 84 | return maxTPos.getZ(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/IntegerPositionRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import java.sql.SQLData; 4 | import java.sql.SQLException; 5 | import java.sql.SQLInput; 6 | import java.sql.SQLOutput; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since ${version} 11 | **/ 12 | public class IntegerPositionRecord implements SQLData { 13 | private int x, y, z, d; 14 | 15 | public IntegerPositionRecord() { 16 | } 17 | 18 | public IntegerPositionRecord(int x, int y, int z, int d) { 19 | this.x = x; 20 | this.y = y; 21 | this.z = z; 22 | this.d = d; 23 | } 24 | 25 | @Override 26 | public String getSQLTypeName() { 27 | return "ipos"; 28 | } 29 | 30 | @Override 31 | public void readSQL(SQLInput stream, String typeName) throws SQLException { 32 | this.x = stream.readInt(); 33 | this.y = stream.readInt(); 34 | this.z = stream.readInt(); 35 | this.d = stream.readInt(); 36 | } 37 | 38 | @Override 39 | public void writeSQL(SQLOutput stream) throws SQLException { 40 | stream.writeInt(x); 41 | stream.writeInt(y); 42 | stream.writeInt(z); 43 | stream.writeInt(d); 44 | } 45 | 46 | public int getX() { 47 | return x; 48 | } 49 | 50 | public int getY() { 51 | return y; 52 | } 53 | 54 | public int getZ() { 55 | return z; 56 | } 57 | 58 | public int getD() { 59 | return d; 60 | } 61 | 62 | public void setX(int x) { 63 | this.x = x; 64 | } 65 | 66 | public void setY(int y) { 67 | this.y = y; 68 | } 69 | 70 | public void setZ(int z) { 71 | this.z = z; 72 | } 73 | 74 | public void setD(int d) { 75 | this.d = d; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/InventoryLookupRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.server.world.ServerWorld; 5 | import net.minecraft.util.math.BlockPos; 6 | import net.minecraft.util.registry.Registry; 7 | 8 | import java.time.Instant; 9 | import java.util.UUID; 10 | 11 | /** 12 | * @author Ampflower 13 | * @since ${version} 14 | **/ 15 | public final class InventoryLookupRecord extends LookupRecord { 16 | public final ServerWorld targetWorld; 17 | public final BlockPos minTPos, maxTPos; 18 | public final Item item; 19 | public final UUID targetUserId, targetEntityId; 20 | 21 | public InventoryLookupRecord(ServerWorld world, BlockPos minPosition, BlockPos maxPosition, UUID causeUuid, Instant minTime, Instant maxTime, 22 | ServerWorld targetWorld, BlockPos minTPos, BlockPos maxTPos, Item item, UUID targetUserId, UUID targetEntityId, int page, int flags) { 23 | super(world, minPosition, maxPosition, causeUuid, minTime, maxTime, page, flags); 24 | this.targetWorld = targetWorld; 25 | this.minTPos = minTPos; 26 | this.maxTPos = maxTPos; 27 | this.item = item; 28 | this.targetUserId = targetUserId; 29 | this.targetEntityId = targetEntityId; 30 | } 31 | 32 | public InventoryLookupRecord(ServerWorld world, BlockPos pos, int page) { 33 | this(null, null, null, null, null, null, world, pos, null, null, null, null, page, FLAG_T_AT); 34 | } 35 | 36 | public String item() { 37 | return Registry.ITEM.getId(item).toString(); 38 | } 39 | 40 | @Override 41 | public RecordType getType() { 42 | return RecordType.LOOKUP_INVENTORY; 43 | } 44 | 45 | @Override 46 | public Class getOutput() { 47 | return InventoryRecord.class; 48 | } 49 | 50 | public int minTX() { 51 | return minTPos.getX(); 52 | } 53 | 54 | public int minTY() { 55 | return minTPos.getY(); 56 | } 57 | 58 | public int minTZ() { 59 | return minTPos.getZ(); 60 | } 61 | 62 | public int maxTX() { 63 | return maxTPos.getX(); 64 | } 65 | 66 | public int maxTY() { 67 | return maxTPos.getY(); 68 | } 69 | 70 | public int maxTZ() { 71 | return maxTPos.getZ(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/PlymouthRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import net.minecraft.text.Text; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Record interface. Only carries methods for flags and type. 8 | *

9 | * Most records typically hold the cause and target, including world, pos and entity's name and UUID. 10 | * 11 | * @author Ampflower 12 | * @see BlockRecord 13 | * @see DeathRecord 14 | * @see InventoryRecord 15 | * @see LookupRecord 16 | * @see BlockLookupRecord 17 | * @see InventoryLookupRecord 18 | * @since ${version} 19 | **/ 20 | public interface PlymouthRecord { 21 | /** 22 | * Creates a formatted text object to display the time, cause, target and position of the record. 23 | * 24 | * @return Translatable text for use with rendering the logs and sending to the client. 25 | */ 26 | @NotNull 27 | Text toText(); 28 | 29 | /** 30 | * Creates a formatted text object to display the time, cause and target of the record. 31 | * 32 | * @return Translatable text for use with rendering the logs and sending to the client. 33 | */ 34 | @NotNull 35 | default Text toTextNoPosition() { 36 | return toText(); 37 | } 38 | 39 | /** 40 | * Bitwise flag of what the record is for. 41 | * Please see the individual classes for what the bitwise flags are for. 42 | * 43 | * @return flags if overridden, else 0 by default. 44 | * @see InventoryRecord#flags() 45 | * @see LookupRecord#flags() 46 | */ 47 | // Please link any records that overrides flags here with `@see #flags()`. 48 | // A link to the field is sufficient should there only be a field implementing this. 49 | 50 | // The name is purposefully `flags` to allow a passive override by record. 51 | default int flags() { 52 | return 0; 53 | } 54 | 55 | /** 56 | * The record that this is for. 57 | * Note, implementations may make hard assumptions of the underlying class. 58 | * 59 | * @return Type of record, indicating the class of this record. 60 | */ 61 | RecordType getType(); 62 | 63 | /** 64 | * This method should format the string similarly to the following example. 65 | * PlymouthRecord{flags=0, name='A record'} 66 | * 67 | * @return The class and fields formatted for ease of reading. 68 | */ 69 | String toString(); 70 | } 71 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/RecordType.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | /** 4 | * The type of record. Used for switches and casting. 5 | * 6 | * @author Ampflower 7 | * @since ${version} 8 | **/ 9 | public enum RecordType { 10 | /** 11 | * Indicates that the class is a {@link BlockRecord}. 12 | */ 13 | BLOCK, 14 | /** 15 | * Indicates that the class is a {@link DeathRecord}. 16 | */ 17 | DEATH, 18 | /** 19 | * Indicates that the class is a {@link InventoryRecord}. 20 | */ 21 | INVENTORY, 22 | /** 23 | * Indicates that the class is a {@link LookupRecord}. 24 | */ 25 | @Deprecated(forRemoval = true) 26 | LOOKUP, 27 | LOOKUP_BLOCK, 28 | LOOKUP_DEATH, 29 | LOOKUP_INVENTORY 30 | } 31 | -------------------------------------------------------------------------------- /ply-database/src/main/java/gay/ampflower/plymouth/database/records/TargetRecord.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.database.records; 2 | 3 | import gay.ampflower.plymouth.common.UUIDHelper; 4 | import gay.ampflower.plymouth.database.DatabaseHelper; 5 | import gay.ampflower.plymouth.database.Target; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.util.math.Vec3d; 10 | import net.minecraft.world.World; 11 | 12 | import java.util.Objects; 13 | import java.util.UUID; 14 | 15 | /** 16 | * A minimal implementation of {@link Target} for use with passing along to record constructors and anything that accepts Target. 17 | *

18 | * Holds the world, position, name, user ID and entity ID. 19 | * 20 | * @author Ampflower 21 | * @since ${version} 22 | **/ 23 | public final class TargetRecord implements Target { 24 | public final World world; 25 | public final BlockPos pos; 26 | public final Vec3d dpos; 27 | // This isn't counted in the hash, it is effectively equal regardless of if the name matches. 28 | public final String name; 29 | public final UUID userId, entityId; 30 | 31 | public TargetRecord(World world, BlockPos pos, Vec3d dpos, String name, UUID userId, UUID entityId) { 32 | this.world = world; 33 | this.pos = pos == null ? dpos == null ? null : new BlockPos(dpos) : pos.toImmutable(); 34 | this.dpos = dpos == null ? pos == null ? null : Vec3d.ofCenter(pos) : dpos; 35 | if (name == null && userId != null) throw new Error("wtf?"); 36 | this.name = name; 37 | this.userId = userId; 38 | this.entityId = entityId; 39 | } 40 | 41 | public TargetRecord(World world, BlockPos pos) { 42 | this(world, pos, null, null, null, null); 43 | } 44 | 45 | public TargetRecord(Entity entity) { 46 | this(entity.world, entity.getBlockPos(), entity.getPos(), DatabaseHelper.getName(entity), UUIDHelper.getUUID(entity), entity instanceof PlayerEntity ? null : entity.getUuid()); 47 | } 48 | 49 | public static TargetRecord ofEntityNoPosition(Entity entity) { 50 | return new TargetRecord(null, null, null, DatabaseHelper.getName(entity), UUIDHelper.getUUID(entity), entity instanceof PlayerEntity ? null : entity.getUuid()); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (!(o instanceof TargetRecord that)) return false; 57 | return Objects.equals(world, that.world) && Objects.equals(pos, that.pos) && Objects.equals(userId, that.userId) && Objects.equals(entityId, that.entityId); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | int i = Objects.hashCode(world); 63 | i = 31 * i + Objects.hashCode(pos); 64 | i = 31 * i + Objects.hashCode(dpos); 65 | i = 31 * i + Objects.hashCode(userId); 66 | return 31 * i + Objects.hashCode(entityId); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "TargetRecord{" + 72 | "world=" + world + 73 | ", pos=" + pos + 74 | ", name='" + name + '\'' + 75 | ", userId=" + userId + 76 | ", entityId=" + entityId + 77 | '}'; 78 | } 79 | 80 | @Override 81 | public TargetRecord plymouth$toRecord() { 82 | return this; 83 | } 84 | 85 | @Override 86 | public World ply$world() { 87 | return world; 88 | } 89 | 90 | @Override 91 | public BlockPos ply$pos3i() { 92 | return pos; 93 | } 94 | 95 | @Override 96 | public Vec3d ply$pos3d() { 97 | return dpos; 98 | } 99 | 100 | @Override 101 | public String ply$name() { 102 | return name; 103 | } 104 | 105 | @Override 106 | public UUID ply$userId() { 107 | return userId; 108 | } 109 | 110 | @Override 111 | public UUID ply$entityId() { 112 | return entityId; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ply-database/src/main/resources/assets/plymouth-database/lang/en_us.json: -------------------------------------------------------------------------------- 1 | ../../../data/plymouth-database/lang/en_us.json -------------------------------------------------------------------------------- /ply-database/src/main/resources/data/plymouth-database/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "plymouth.tracker.user.unknown": "Unknown User.", 3 | "plymouth.tracker.action.broke": "broke", 4 | "plymouth.tracker.action.placed": "placed", 5 | "plymouth.tracker.action.took": "took", 6 | "plymouth.tracker.action.used": "used", 7 | "plymouth.tracker.record.block": "%s: %s %s %s @ %s", 8 | "plymouth.tracker.record.block.nopos": "%s: %s %s %s", 9 | "plymouth.tracker.record.death": "%s: %s killed %s @ %s", 10 | "plymouth.tracker.record.death.nopos": "%s: %s killed %s", 11 | "plymouth.tracker.record.inventory": "%s: %s %s %s %s @ %s", 12 | "plymouth.tracker.record.inventory.nopos": "%s: %s %s %s %s", 13 | "plymouth.tracker.record.lookup": "globally [%s]", 14 | "plymouth.tracker.record.lookup.by": "by %s [%s]", 15 | "plymouth.tracker.record.lookup.time": "during %s - %s [%s]", 16 | "plymouth.tracker.record.lookup.time.by": "during %s - %s by %s [%s]", 17 | "plymouth.tracker.record.lookup.at": "at %s [%s]", 18 | "plymouth.tracker.record.lookup.at.by": "at %s by %s [%s]", 19 | "plymouth.tracker.record.lookup.at.time": "at %s during %s - %s [%s]", 20 | "plymouth.tracker.record.lookup.at.time.by": "at %s during %s - %s by %s [%s]", 21 | "plymouth.tracker.record.lookup.area": "around %s - %s [%s]", 22 | "plymouth.tracker.record.lookup.area.by": "around %s - %s by %s [%s]", 23 | "plymouth.tracker.record.lookup.area.time": "around %s - %s during %s - %s [%s]", 24 | "plymouth.tracker.record.lookup.area.time.by": "around %s - %s during %s - %s by %s [%s]", 25 | "plymouth.tracker.record.lookup.invalid": "invalid" 26 | } -------------------------------------------------------------------------------- /ply-database/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-database", 4 | "version": "${version}", 5 | "name": "Plymouth: Database", 6 | "description": "Database API for the Locking and Tracker modules.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/Modflower/plymouth-fabric", 12 | "discord": "https://discord.gg/EmPS9y9" 13 | }, 14 | "environment": "*", 15 | "entrypoints": { 16 | "main": [ 17 | "gay.ampflower.plymouth.database.DatabaseHelper::init" 18 | ] 19 | }, 20 | "depends": { 21 | "minecraft": ">=${minecraft_required}", 22 | "plymouth-common": "^${project_version}" 23 | } 24 | } -------------------------------------------------------------------------------- /ply-database/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 7, 4 | "description": "Plymouth: Database" 5 | } 6 | } -------------------------------------------------------------------------------- /ply-debug/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Debug 2 |

3 | 4 | # Plymouth: Debug 5 | 6 | A debugging mod for the anti-xray engine, complete with defeating the purpose of the anti-xray engine. 7 | 8 | This mod is not intended for production use. This will expose any and all hidden blocks should both the client and 9 | server have this mod present. 10 | 11 | ## Usage 12 | 13 | If the anti-xray engine is not outputting the expected response, you can boot up the debug client by setting the 14 | classpath to `ply-debug`. 15 | 16 | As you start up a world, you'll immediately see multitudes of boxes of three coloured boxes as the world begins to 17 | update. 18 | 19 | - `Red`: The anti-xray engine has been updated at that position. 20 | - `Green`: The anti-xray engine has set the block at that position. 21 | - `Blue`: The anti-xray engine has tested the block at that position. 22 | - `Yellow`: A world update has occurred to the client at that position. 23 | 24 | If you wish to see the active mask for a given chunk, you can the command `/mdump`, which will send the entire mask to 25 | the client, which will promptly render it. 26 | 27 | If you wish to stop seeing the active mask, run the command `/plymouth debug anti-xray clear` 28 | 29 |
-------------------------------------------------------------------------------- /ply-debug/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } 7 | 8 | val jupiter_version: String by project 9 | val fabric_api_version: String by project 10 | val fabric_permissions_version: String by project 11 | 12 | repositories { 13 | maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } 14 | maven { url = uri("https://jitpack.io") } 15 | } 16 | 17 | dependencies { 18 | modImplementation("com.github.Modflower", "bytecode-junkie", "v0.3.2") 19 | implementation(project(":utilities")) 20 | // implementation(project(":database")) 21 | implementation(project(":ply-utilities", configuration = "namedElements")) 22 | implementation(project(":ply-anti-xray", configuration = "namedElements")) 23 | implementation(project(":ply-common", configuration = "namedElements")) 24 | // implementation(project(":ply-database")) 25 | implementation(project(":ply-locking", configuration = "namedElements")) 26 | // implementation(project(":ply-tracker")) 27 | modImplementation("net.fabricmc.fabric-api", "fabric-api", fabric_api_version) 28 | modImplementation("me.lucko", "fabric-permissions-api", fabric_permissions_version) 29 | } -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/Debug.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug; 2 | 3 | import io.netty.buffer.Unpooled; 4 | import net.fabricmc.api.ModInitializer; 5 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import gay.ampflower.plymouth.debug.anti_xray.AntiXrayDebugger; 8 | import net.minecraft.SharedConstants; 9 | import net.minecraft.network.PacketByteBuf; 10 | import net.minecraft.util.Identifier; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.lang.invoke.MethodHandles; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * The primary initializer for the debug server. 19 | * 20 | * @author Ampflower 21 | * @since 0.0.0 22 | */ 23 | public class Debug implements ModInitializer { 24 | public static final Logger logger = LoggerFactory.getLogger("Plymouth: Debug"); 25 | private static final StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 26 | private static final MethodHandles.Lookup self = MethodHandles.lookup(); 27 | 28 | @Override 29 | public void onInitialize() { 30 | SharedConstants.isDevelopment = Fusebox.isEnabled("minecraft.development"); 31 | var loader = FabricLoader.getInstance(); 32 | if (loader.isModLoaded("plymouth-anti-xray")) { 33 | if (Fusebox.isEnabled("antiXrayDebug")) { 34 | tryOrLog(AntiXrayDebugger::initialise, "AntiXray found but cannot be loaded."); 35 | } else { 36 | logger.info("Anti-Xray debugging is disabled. Add to the config, `antiXrayDebug=true`, if you wish to debug the anti-xray engine."); 37 | } 38 | } 39 | } 40 | 41 | public static void tryOrLog(Runnable callable, String message) { 42 | try { 43 | callable.run(); 44 | } catch (LinkageError error) { 45 | logger.error(message, error); 46 | } 47 | } 48 | 49 | public static void send(Identifier id, long pos) { 50 | for (var p : AntiXrayDebugger.players) { 51 | ServerPlayNetworking.send(p.player, id, new PacketByteBuf(Unpooled.copyLong(pos))); 52 | } 53 | } 54 | 55 | public static void printRichStack() { 56 | logger.info("Stacktrace at head\n{}", (String) walker.walk(stream -> stream.map(Debug::mux).collect(Collectors.joining("\n")))); 57 | } 58 | 59 | private static String mux(StackWalker.StackFrame frame) { 60 | // try { 61 | // var method = frame.getDeclaringClass().getDeclaredMethod(frame.getMethodName(), frame.getMethodType().parameterArray()); 62 | // method.getAnnotation() 63 | // } catch (NoSuchMethodException | SecurityException exception) { 64 | // ; 65 | // } 66 | // self.findVirtual(frame.getDeclaringClass(), frame.getMethodName(), frame.getMethodType()). 67 | // MethodHandles.reflectAs() 68 | // frame.getMethodType() 69 | return frame.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/DebugClient.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug; 2 | 3 | import net.fabricmc.api.ClientModInitializer; 4 | import net.fabricmc.api.EnvType; 5 | import net.fabricmc.api.Environment; 6 | import gay.ampflower.plymouth.debug.anti_xray.AntiXrayClientDebugger; 7 | import gay.ampflower.plymouth.debug.misc.BoundingBoxDebugClient; 8 | import gay.ampflower.plymouth.debug.misc.MiscDebugClient; 9 | import gay.ampflower.plymouth.debug.misc.ReloadDebugClient; 10 | 11 | import static gay.ampflower.plymouth.debug.Debug.tryOrLog; 12 | 13 | /** 14 | * The primary initializer for the debug client. 15 | * 16 | * @author Ampflower 17 | * @since 0.0.0 18 | */ 19 | @Environment(EnvType.CLIENT) 20 | public class DebugClient implements ClientModInitializer { 21 | 22 | @Override 23 | public void onInitializeClient() { 24 | tryOrLog(ReloadDebugClient::initialise, "Couldn't load reload command."); 25 | tryOrLog(AntiXrayClientDebugger::initialise, "AntiXray client debugger cannot be loaded."); 26 | tryOrLog(MiscDebugClient::initialise, "Misc client debugger cannot be loaded."); 27 | tryOrLog(BoundingBoxDebugClient::initialise, "Bounding box client debugger cannot be loaded."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/DebugProfiler.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.api.Environment; 5 | import net.minecraft.client.util.math.MatrixStack; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.util.math.MathHelper; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * @author Ampflower 13 | * @since 0.0.0 14 | */ 15 | @Environment(EnvType.CLIENT) 16 | public class DebugProfiler { 17 | private final long[] points; 18 | private final int r, g, b; 19 | private final float m, m1, s; 20 | private final int mask; 21 | private int index; 22 | 23 | public DebugProfiler(int length, int r, int g, int b, float m, float s) { 24 | int len = 1 << MathHelper.ceilLog2(length); 25 | this.points = new long[len]; 26 | mask = len - 1; 27 | this.r = r; 28 | this.g = g; 29 | this.b = b; 30 | this.m = -m; 31 | this.m1 = m + 1; 32 | this.s = s; 33 | } 34 | 35 | public DebugProfiler(int length, int r, int g, int b, float m) { 36 | this(length, r, g, b, m, 1F); 37 | } 38 | 39 | public void render(MatrixStack stack) { 40 | stack.push(); 41 | stack.scale(s, s, s); 42 | for (int i = 0; i < points.length; i++) { 43 | // stack.push(); 44 | long pos = points[i]; 45 | int x = BlockPos.unpackLongX(pos); 46 | int y = BlockPos.unpackLongY(pos); 47 | int z = BlockPos.unpackLongZ(pos); 48 | // stack.translate(BlockPos.unpackLongX(pos), BlockPos.unpackLongY(pos), BlockPos.unpackLongZ(pos)); 49 | RenderBatch.drawWireBox(stack.peek(), m + x, m + y, m + z, m1 + x, m1 + y, m1 + z, r, g, b, (int) ((i + 1 == (index & mask) ? 0.75F : 0.25F * ((float) ((i - index) & mask) / points.length)) * 255F), false); //, false, false, false); 50 | //WorldRenderer.drawBox(stack, consumer, m, m, m, m1, m1, m1, r, g, b, i + 1 == (index & mask) ? 0.75F : 0.25F * ((float) ((i - index) & mask) / points.length)); 51 | // stack.pop(); 52 | } 53 | stack.pop(); 54 | } 55 | 56 | public void push(long point) { 57 | points[index++ & mask] = point; 58 | } 59 | 60 | public void clear() { 61 | Arrays.fill(points, 0); 62 | index = 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/anti_xray/AntiXrayDebugger.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.anti_xray; 2 | 3 | import io.netty.buffer.Unpooled; 4 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 5 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 6 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 7 | import gay.ampflower.plymouth.antixray.ShadowChunk; 8 | import net.minecraft.network.PacketByteBuf; 9 | import net.minecraft.server.command.ServerCommandSource; 10 | import net.minecraft.server.network.ServerPlayNetworkHandler; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.minecraft.util.Identifier; 13 | import net.minecraft.util.math.MathHelper; 14 | 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | import static net.minecraft.server.command.CommandManager.literal; 19 | 20 | /** 21 | * @author Ampflower 22 | * @since 0.0.0 23 | */ 24 | public class AntiXrayDebugger { 25 | public static final Identifier 26 | debugAntiXraySet = new Identifier("plymouth-debug", "set"), 27 | debugAntiXrayUpdate = new Identifier("plymouth-debug", "update"), 28 | debugAntiXrayTest = new Identifier("plymouth-debug", "test"), 29 | debugAntiXrayMask = new Identifier("plymouth-debug", "mask"); 30 | public static final Set players = new HashSet<>(); 31 | 32 | public static boolean canSendDebugInformation(ServerCommandSource source) { 33 | return source.getEntity() instanceof ServerPlayerEntity player && players.contains(player.networkHandler); 34 | } 35 | 36 | public static void initialise() { 37 | ServerPlayConnectionEvents.JOIN.register((player, packetSender, server) -> { 38 | if (ServerPlayNetworking.canSend(player, AntiXrayDebugger.debugAntiXrayUpdate)) 39 | AntiXrayDebugger.players.add(player); 40 | }); 41 | CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("mdump").requires(AntiXrayDebugger::canSendDebugInformation).executes(ctx -> { 42 | var player = ctx.getSource().getPlayerOrThrow(); 43 | int cx = MathHelper.floor(player.getX()) >> 4, cz = MathHelper.floor(player.getZ()) >> 4; 44 | var mask = ((ShadowChunk) player.world.getChunk(cx, cz)).plymouth$getShadowMask(); 45 | var packet = new PacketByteBuf(Unpooled.buffer()).writeVarInt(cx).writeVarInt(cz).writeLongArray(mask.toLongArray()); 46 | ServerPlayNetworking.send(player, AntiXrayDebugger.debugAntiXrayMask, packet); 47 | return 1; 48 | }))); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/misc/ReloadDebugClient.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.misc; 2 | 3 | import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; 4 | import gay.ampflower.plymouth.debug.Fusebox; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; 10 | 11 | /** 12 | * @author Ampflower 13 | * @since ${version} 14 | **/ 15 | public class ReloadDebugClient { 16 | 17 | private static final List reloadables = new ArrayList<>(); 18 | 19 | public static void initialise() { 20 | ClientCommandRegistrationCallback.EVENT.register((DISPATCHER, registryAccess) -> 21 | DISPATCHER.register(literal("pdbc").then(literal("reload").executes(ctx -> { 22 | Fusebox.reinit(); 23 | for (final var reloadable : reloadables) reloadable.run(); 24 | return 1; 25 | }))) 26 | ); 27 | } 28 | 29 | public static void addReloadable(Runnable runnable) { 30 | reloadables.add(runnable); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/mixins/client/MixinBustAntiXrayClientPlayNetworkHandler.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.mixins.client; 2 | 3 | import gay.ampflower.plymouth.debug.anti_xray.AntiXrayClientDebugger; 4 | import net.minecraft.client.network.ClientPlayNetworkHandler; 5 | import net.minecraft.network.packet.s2c.play.*; 6 | import net.minecraft.util.math.BlockPos; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Pseudo; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | /** 14 | * @author Ampflower 15 | * @since 0.0.0 16 | */ 17 | @Pseudo 18 | @Mixin(ClientPlayNetworkHandler.class) 19 | public class MixinBustAntiXrayClientPlayNetworkHandler { 20 | @Inject(method = "onBlockUpdate", at = @At("RETURN")) 21 | private void plymouth$onBlockUpdate(BlockUpdateS2CPacket packet, CallbackInfo cbi) { 22 | AntiXrayClientDebugger.onBlockDelta.push(packet.getPos().asLong()); 23 | } 24 | 25 | @Inject(method = "onChunkDeltaUpdate", at = @At("RETURN")) 26 | private void plymouth$onDeltaUpdate(ChunkDeltaUpdateS2CPacket packet, CallbackInfo cbi) { 27 | packet.visitUpdates((pos, state) -> AntiXrayClientDebugger.onBlockDelta.push(pos.asLong())); 28 | } 29 | 30 | @Inject(method = "onChunkData", at = @At("RETURN")) 31 | private void plymouth$onChunkData(ChunkDataS2CPacket packet, CallbackInfo cbi) { 32 | AntiXrayClientDebugger.onChunkLoad.push(BlockPos.asLong(packet.getX(), 0, packet.getZ())); 33 | packet.getChunkData().getBlockEntities(packet.getX(), packet.getZ()).accept((pos, $1, $2) -> 34 | AntiXrayClientDebugger.onChunkBlockEntity.push(pos.asLong())); 35 | } 36 | 37 | @Inject(method = "onBlockEntityUpdate", at = @At("RETURN")) 38 | private void plymouth$onBlockEntityUpdate(BlockEntityUpdateS2CPacket packet, CallbackInfo cbi) { 39 | AntiXrayClientDebugger.onBlockEntityUpdate.push(packet.getPos().asLong()); 40 | } 41 | 42 | @Inject(method = "onBlockEvent", at = @At("RETURN")) 43 | private void plymouth$onBlockEvent(BlockEventS2CPacket packet, CallbackInfo cbi) { 44 | AntiXrayClientDebugger.onBlockEvent.push(packet.getPos().asLong()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/mixins/client/MixinBustLoadingMinecraftClient.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.mixins.client; 2 | 3 | import net.minecraft.client.MinecraftClient; 4 | import net.minecraft.client.gui.screen.DownloadingTerrainScreen; 5 | import net.minecraft.client.gui.screen.Screen; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | /** 14 | * @author Ampflower 15 | * @since ${version} 16 | **/ 17 | @Mixin(MinecraftClient.class) 18 | public class MixinBustLoadingMinecraftClient { 19 | @Shadow 20 | @Nullable 21 | public Screen currentScreen; 22 | 23 | @Inject(method = "setScreen", at = @At("HEAD"), cancellable = true) 24 | private void plymouth$debug$bustLoadingScreen(Screen screen, CallbackInfo ci) { 25 | if (screen instanceof DownloadingTerrainScreen) { 26 | var current = this.currentScreen; 27 | 28 | if (current != null) { 29 | current.removed(); 30 | this.currentScreen = null; 31 | } 32 | screen.removed(); 33 | ci.cancel(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/mixins/database/MixinDatabaseHelper.java.exclude: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.mixins.database; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Plymouth; 5 | import gay.ampflower.plymouth.debug.database.PlymouthLoggingDelegate; 6 | import org.objectweb.asm.Opcodes; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | /** 13 | * @author Ampflower 14 | * @since ${version} 15 | **/ 16 | @Mixin(DatabaseHelper.class) 17 | public class MixinDatabaseHelper { 18 | @Shadow 19 | public static Plymouth database; 20 | 21 | @Redirect(method = "", at = @At(value = "FIELD", target = "Lgay/ampflower/plymouth/database/DatabaseHelper;database:Lgay/ampflower/plymouth/database/Plymouth;", opcode = Opcodes.PUTSTATIC)) 22 | private static void debug$redirect$database$store(Plymouth plymouth) { 23 | database = new PlymouthLoggingDelegate(plymouth); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/mixins/tracker/MixinExplosion.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.mixins.tracker; 2 | 3 | import gay.ampflower.plymouth.debug.Debug; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.world.World; 6 | import net.minecraft.world.explosion.Explosion; 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 | /** 13 | * Logs if an explosion is missing an entity if it's destructive. 14 | * 15 | * @author Ampflower 16 | * @since ${version} 17 | **/ 18 | @Mixin(Explosion.class) 19 | public class MixinExplosion { 20 | @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V", at = @At("RETURN")) 21 | private void plymouth$logIfMissingEntity(World world, Entity entity, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, CallbackInfo ci) { 22 | if (entity == null && destructionType != Explosion.DestructionType.KEEP) 23 | Debug.logger.warn("Missing entity for given destructive explosion {world={}, x={}, y={}, z={}, p={}, fire={}, destruction={}}", world, x, y, z, power, createFire, destructionType, new Throwable("Stack trace.")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ply-debug/src/main/java/gay/ampflower/plymouth/debug/mixins/tracker/MixinSlot.java.exclude: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.debug.mixins.tracker; 2 | 3 | import gay.ampflower.plymouth.database.Target; 4 | import gay.ampflower.plymouth.tracker.Tracker; 5 | import net.minecraft.inventory.Inventory; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.screen.slot.Slot; 8 | import org.spongepowered.asm.mixin.Final; 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 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | 16 | /** 17 | * @author Ampflower 18 | * @since 0.0.0 19 | */ 20 | @Mixin(Slot.class) 21 | public class MixinSlot { 22 | @Shadow 23 | @Final 24 | private int index; 25 | 26 | @Shadow 27 | @Final 28 | public Inventory inventory; 29 | 30 | @Shadow 31 | public int id; 32 | 33 | @Shadow 34 | @Final 35 | public int x; 36 | 37 | @Shadow 38 | @Final 39 | public int y; 40 | 41 | @Inject(method = "onQuickTransfer", at = @At("HEAD")) 42 | private void plymouth$onStackChanged(ItemStack a, ItemStack b, CallbackInfo cbi) { 43 | if (inventory instanceof Target target && !target.ply$world().isClient) 44 | Tracker.logger.info("{} had items changed out: {}, {}", this, a, b, new Throwable()); 45 | } 46 | 47 | @Inject(method = "setStack", at = @At("HEAD")) 48 | private void plymouth$onSetStack(ItemStack a, CallbackInfo cbi) { 49 | if (inventory instanceof Target target && !target.ply$world().isClient) 50 | Tracker.logger.info("{} had items set to {}", this, a, new Throwable()); 51 | } 52 | 53 | @Inject(method = "insertStack(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;", at = @At("HEAD")) 54 | private void plymouth$onSetStack(ItemStack a, CallbackInfoReturnable cbi) { 55 | if (inventory instanceof Target target && !target.ply$world().isClient) 56 | Tracker.logger.info("{} had items set to {}", this, a, new Throwable()); 57 | } 58 | 59 | @Inject(method = "insertStack(Lnet/minecraft/item/ItemStack;I)Lnet/minecraft/item/ItemStack;", at = @At("HEAD")) 60 | private void plymouth$onSetStack(ItemStack a, int i, CallbackInfoReturnable cbi) { 61 | if (inventory instanceof Target target && !target.ply$world().isClient) 62 | Tracker.logger.info("{} had items set to {}", this, a, new Throwable()); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return this.getClass() + "{index=" + index + ", inventory=" + inventory + ", id=" + id + ", pos=(" + x + ',' + y + "), stack=" + inventory.getStack(index) + "}"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ply-debug/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-debug", 4 | "version": "${version}", 5 | "name": "Plymouth: Debug", 6 | "description": "Debugging utilities for Plymouth.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/Modflower/plymouth-fabric", 12 | "discord": "https://discord.gg/EmPS9y9" 13 | }, 14 | "environment": "*", 15 | "entrypoints": { 16 | "main": [ 17 | "gay.ampflower.plymouth.debug.Debug" 18 | ], 19 | "client": [ 20 | "gay.ampflower.plymouth.debug.DebugClient" 21 | ] 22 | }, 23 | "mixins": [ 24 | "plymouth-debug.mixin.json" 25 | ], 26 | "depends": { 27 | "minecraft": ">=${minecraft_required}" 28 | } 29 | } -------------------------------------------------------------------------------- /ply-debug/src/main/resources/plymouth-debug.mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "gay.ampflower.plymouth.debug.mixins", 4 | "plugin": "gay.ampflower.plymouth.debug.MixinConfig", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "client.MixinBustLoadingMinecraftClient", 8 | "tracker.MixinExplosion" 9 | ], 10 | "client": [ 11 | "client.MixinBustAntiXrayClientPlayNetworkHandler" 12 | ], 13 | "injectors": { 14 | "defaultRequire": 1 15 | } 16 | } -------------------------------------------------------------------------------- /ply-locking/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Locking 2 |
3 | 4 | # Plymouth: Locking 5 | 6 | A claims system that allows one to lock their chests with a sneak+use. 7 | 8 | ## Downloads 9 | 10 | You may download Locking from [Modrinth](https://modrinth.com/mod/plymouth-locking) or 11 | from [GitHub Releases](https://github.com/Modflower/plymouth-fabric/releases). 12 | 13 | ## Usage 14 | 15 | Drop the mod into the mods folder of your server then boot it up. No configuring nor commands required. 16 | 17 | ### In-world 18 | 19 | #### (Un)Locking a block entity 20 | 21 | You can lock a chest by shift+right-clicking it with an empty hand. This will lock the chest to be only accessible to 22 | you. You can also unlock a chest using the same action. 23 | 24 | ### Commands 25 | 26 | The position argument is optional for these commands. If you choose to omit the position, you must interact with the 27 | block after executing. If you do define a position, you don't own the block and you are near the block, you will 28 | automatically own the block as if you shift+right-clicked it. 29 | 30 | Permissions within the following commands mean the following: 31 | 32 | - `r`: Read from block 33 | - `w`: Write into block 34 | - `d`: Destroy block 35 | - `p`: Permissions management 36 | 37 | The expected syntax for permissions is similar to chmod, where it accepts either `rwdx`, or `+r-w` to allow setting all 38 | permissions, or to allow read and deny write respectively. 39 | 40 | The following commands is what to use for permission management: 41 | 42 | - `/lock (at |interact) add `: Adds players to a block. 43 | - `/lock (at |interact) remove `: Removes players from a block. 44 | - `/lock (at |interact) modify `: Sets permissions on a block. 45 | - `/lock (at |interact) get`: Gets the information of the block. 46 | 47 | Convenience commands: 48 | 49 | - `/trust `: Same as `/lock interact add` 50 | - `/distrust `: Same as `/lock interact remove` 51 | 52 | ### Permissions 53 | 54 | - `plymouth.locking.lock`: Be able to lock block entities. Default is true. 55 | - `plymouth.locking.bypass.read`: Bypass read permission. Default is OP level 2. 56 | - `plymouth.locking.bypass.write`: Bypass write permission. Default is OP level 2. 57 | - `plymouth.locking.bypass.delete`: Bypass delete permission. Default is OP level 2. 58 | - `plymouth.locking.bypass.permissions`: Bypass permissions. Default is OP level 2. 59 | 60 |
-------------------------------------------------------------------------------- /ply-locking/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } 7 | 8 | val minecraft_version: String by project 9 | val yarn_mappings: String by project 10 | val loader_version: String by project 11 | val jupiter_version: String by project 12 | val fabric_api_version: String by project 13 | val fabric_permissions_version: String by project 14 | val project_version: String by project 15 | 16 | val isRelease = System.getenv("BUILD_RELEASE").toBoolean() 17 | val isActions = System.getenv("GITHUB_ACTIONS").toBoolean() 18 | val baseVersion: String = "$project_version+mc.$minecraft_version" 19 | 20 | group = "gay.ampflower" 21 | version = when { 22 | isRelease -> baseVersion 23 | isActions -> "$baseVersion-build.${System.getenv("GITHUB_RUN_NUMBER")}-commit.${System.getenv("GITHUB_SHA").substring(0, 7)}-branch.${System.getenv("GITHUB_REF")?.substring(11)?.replace('/', '.') ?: "unknown"}" 24 | else -> "$baseVersion-build.local" 25 | } 26 | 27 | repositories { 28 | maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } 29 | } 30 | 31 | dependencies { 32 | implementation(project(":ply-common", configuration = "namedElements")) 33 | modImplementation(fabricApi.module("fabric-command-api-v2", fabric_api_version)) 34 | modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabric_api_version)) 35 | modImplementation("me.lucko", "fabric-permissions-api", fabric_permissions_version) 36 | } -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/ILockable.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking; 2 | 3 | import gay.ampflower.plymouth.locking.handler.IPermissionHandler; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public interface ILockable { 7 | @Nullable 8 | IPermissionHandler plymouth$getPermissionHandler(); 9 | 10 | void plymouth$setPermissionHandler(@Nullable IPermissionHandler handler); 11 | 12 | default boolean plymouth$isOwned() { 13 | return plymouth$getPermissionHandler() != null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/handler/BasicPermissionHandler.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.handler; 2 | 3 | import gay.ampflower.plymouth.locking.Locking; 4 | import net.minecraft.nbt.NbtCompound; 5 | import net.minecraft.server.command.ServerCommandSource; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | import static net.minecraft.text.Text.translatable; 12 | 13 | /** 14 | * Permission handler designed for POSIX-like usage. Permission bits are Read, Write, Delete and Permissions, or RWDP. 15 | * 16 | * @author Ampflower 17 | * @since 0.0.0 18 | */ 19 | public class BasicPermissionHandler implements IPermissionHandler { 20 | protected UUID owner; 21 | protected String group; 22 | // 3x rwdp 23 | protected short permissions; 24 | 25 | public BasicPermissionHandler() { 26 | } 27 | 28 | public BasicPermissionHandler(IPermissionHandler handler) { 29 | this(handler.getOwner(), handler.getGroup(), handler.getPermissions()); 30 | } 31 | 32 | public BasicPermissionHandler(UUID owner) { 33 | this(owner, null, (short) Locking.DEFAULT_UMASK); 34 | } 35 | 36 | public BasicPermissionHandler(UUID owner, String group, short permissions) { 37 | this.owner = owner; 38 | this.group = group; 39 | this.permissions = permissions; 40 | } 41 | 42 | @Override 43 | public final @NotNull UUID getOwner() { 44 | return owner; 45 | } 46 | 47 | @Override 48 | public final void setOwner(@NotNull UUID owner) { 49 | // We're requiring non-null to force stuff to explode. 50 | this.owner = Objects.requireNonNull(owner, "owner"); 51 | } 52 | 53 | @Override 54 | public final String getGroup() { 55 | return group; 56 | } 57 | 58 | @Override 59 | public final void setGroup(String group) { 60 | this.group = group; 61 | } 62 | 63 | @Override 64 | public short getPermissions() { 65 | return permissions; 66 | } 67 | 68 | @Override 69 | public void setPermissions(short permissions) { 70 | this.permissions = permissions; 71 | } 72 | 73 | @Override 74 | public void modifyPermissions(int permissions) { 75 | this.permissions = (short) ((this.permissions & ~(permissions >>> 16)) | permissions & 0xFFFF); 76 | } 77 | 78 | @Override 79 | public void fromTag(NbtCompound tag) { 80 | IPermissionHandler.super.fromTag(tag); 81 | // public for legacy handling 82 | permissions = (short) (tag.getShort("permissions") | tag.getByte("public")); 83 | } 84 | 85 | @Override 86 | public void toTag(NbtCompound tag) { 87 | IPermissionHandler.super.toTag(tag); 88 | tag.putShort("permissions", permissions); 89 | } 90 | 91 | @Override 92 | public void dumpLock(ServerCommandSource to) { 93 | // Lock owned by, group, and permissions 94 | to.sendFeedback(translatable("plymouth.locking.dump.basic", owner, group, Locking.toString(permissions), Locking.toString(effectivePermissions(to))), false); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/handler/IAdvancedPermissionHandler.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.handler; 2 | 3 | import net.minecraft.entity.player.PlayerEntity; 4 | 5 | import java.util.Collection; 6 | import java.util.UUID; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since 0.0.0 11 | */ 12 | public interface IAdvancedPermissionHandler extends IPermissionHandler { 13 | void addPlayersByUuid(Collection uuids, byte permissions); 14 | 15 | void removePlayersByUuid(Collection uuids); 16 | 17 | void addPlayers(Collection players, byte permissions); 18 | 19 | void modifyPlayers(Collection players, short permissions); 20 | 21 | void removePlayers(Collection players); 22 | 23 | Collection getPlayersByUuid(); 24 | 25 | void addGroups(Collection groups, byte permissions); 26 | 27 | void removeGroups(Collection groups); 28 | 29 | Collection getGroups(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/MixinBlockEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import gay.ampflower.plymouth.locking.handler.AdvancedPermissionHandler; 6 | import gay.ampflower.plymouth.locking.handler.BasicPermissionHandler; 7 | import gay.ampflower.plymouth.locking.handler.IPermissionHandler; 8 | import net.minecraft.block.entity.BlockEntity; 9 | import net.minecraft.nbt.NbtCompound; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | @Mixin(BlockEntity.class) 18 | public abstract class MixinBlockEntity implements ILockable { 19 | // 1 - Read 20 | // 2 - Write 21 | // 4 - Destroy 22 | // 8 - Modify Permissions 23 | //--------pdwr 24 | 25 | @Unique 26 | private IPermissionHandler permissionHandler; 27 | 28 | @Override 29 | public @Nullable IPermissionHandler plymouth$getPermissionHandler() { 30 | return permissionHandler; 31 | } 32 | 33 | @Override 34 | public void plymouth$setPermissionHandler(@Nullable IPermissionHandler handler) { 35 | this.permissionHandler = handler; 36 | } 37 | 38 | @Inject(method = "readNbt(Lnet/minecraft/nbt/NbtCompound;)V", at = @At("RETURN")) 39 | private void helium$fromTag(NbtCompound nbt, CallbackInfo cbi) { 40 | // LEGACY NOTE: "helium" and "sodium" namespaces are already used in production. 41 | // In order to keep production tile entities from losing their lock, presence of Sodium will be checked if Helium doesn't exist. 42 | var helium = nbt.contains("helium", 10) ? nbt.getCompound("helium") : nbt.contains("sodium", 10) ? nbt.getCompound("sodium") : null; 43 | if (helium != null) { 44 | if (!helium.containsUuid("owner")) { 45 | Locking.logger.warn(":concern: (https://cdn.discordapp.com/emojis/798290111656886283.png?v=1) Helium or sodium tag exists but there is no owner bound?!"); 46 | return; 47 | } 48 | // LEGACY NOTE: "access" is a carry over from helium. DFU is a pain to set up, so a primitive JIT conversion is done instead. 49 | if (helium.contains("access", 9) || helium.contains("players", 9) || helium.contains("groups", 9)) { 50 | permissionHandler = new AdvancedPermissionHandler(); 51 | } else { 52 | permissionHandler = new BasicPermissionHandler(); 53 | } 54 | permissionHandler.fromTag(helium); 55 | } 56 | } 57 | 58 | @Inject(method = "writeNbt", at = @At("RETURN")) 59 | private void helium$toTag(NbtCompound nbt, CallbackInfo cbi) { 60 | if (permissionHandler != null) { 61 | var helium = new NbtCompound(); 62 | permissionHandler.toTag(helium); 63 | // Legacy naming 64 | nbt.put("helium", helium); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/MixinExplosionBehavior.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.world.BlockView; 8 | import net.minecraft.world.explosion.Explosion; 9 | import net.minecraft.world.explosion.ExplosionBehavior; 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 | /** 16 | * @author Ampflower 17 | * @since 0.0.0 18 | **/ 19 | @Mixin(ExplosionBehavior.class) 20 | public class MixinExplosionBehavior { 21 | @Inject(method = "canDestroyBlock", at = @At("HEAD"), cancellable = true) 22 | private void helium$canDestroyBlock(Explosion explosion, BlockView world, BlockPos pos, BlockState state, float power, CallbackInfoReturnable cir) { 23 | if (state.hasBlockEntity() && Locking.canBreak((ILockable) world.getBlockEntity(pos), explosion)) 24 | cir.setReturnValue(false); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/MixinServerWorld.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.handler.IPermissionHandler; 5 | import net.minecraft.entity.player.PlayerEntity; 6 | import net.minecraft.server.world.ServerWorld; 7 | import net.minecraft.text.Text; 8 | import net.minecraft.util.Formatting; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.world.World; 11 | import net.minecraft.world.WorldAccess; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | 17 | import static gay.ampflower.plymouth.locking.Locking.toText; 18 | 19 | /** 20 | * @author Ampflower 21 | * @since ${version} 22 | **/ 23 | @Mixin({ServerWorld.class, World.class}) 24 | public abstract class MixinServerWorld implements WorldAccess { 25 | /** 26 | * @author Ampflower 27 | * @reason Adds a second layer to ensure that locking is effective. 28 | */ 29 | @SuppressWarnings("ConstantConditions") // the permission handler should be not null if owned. 30 | @Inject(method = "canPlayerModifyAt", at = @At("RETURN"), cancellable = true) 31 | public void plymouth$canPlayerModifyAt(PlayerEntity player, BlockPos pos, CallbackInfoReturnable cir) { 32 | if (cir.getReturnValueZ()) { 33 | var be = (ILockable) getBlockEntity(pos); 34 | IPermissionHandler handler; 35 | if (be != null && be.plymouth$isOwned() && !(handler = be.plymouth$getPermissionHandler()).hasAnyPermissions(player.getCommandSource())) { 36 | player.sendMessage(Text.translatable("plymouth.locking.locked", toText(getBlockState(pos).getBlock()), handler.getOwner()).formatted(Formatting.RED), true); 37 | cir.setReturnValue(Boolean.FALSE); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/MixinWorld.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.block.entity.BlockEntity; 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.world.World; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 16 | 17 | @Mixin(World.class) 18 | public abstract class MixinWorld { 19 | 20 | @Shadow 21 | public abstract BlockEntity getBlockEntity(BlockPos pos); 22 | 23 | @Inject(method = "breakBlock(Lnet/minecraft/util/math/BlockPos;ZLnet/minecraft/entity/Entity;I)Z", 24 | at = @At(value = "INVOKE", 25 | target = "Lnet/minecraft/world/World;getFluidState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/fluid/FluidState;", 26 | shift = At.Shift.BEFORE 27 | ), 28 | cancellable = true, 29 | locals = LocalCapture.CAPTURE_FAILEXCEPTION 30 | ) 31 | private void helium$breakBlock(BlockPos pos, boolean drop, Entity breaker, int i, CallbackInfoReturnable cbir, BlockState blockState) { 32 | if (blockState.hasBlockEntity() && !Locking.canBreak((ILockable) getBlockEntity(pos), breaker)) { 33 | cbir.setReturnValue(false); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/entities/MixinEnderDragon.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins.entities; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.block.Material; 6 | import net.minecraft.entity.EntityType; 7 | import net.minecraft.entity.boss.dragon.EnderDragonEntity; 8 | import net.minecraft.entity.mob.MobEntity; 9 | import net.minecraft.registry.tag.BlockTags; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.util.math.Box; 12 | import net.minecraft.util.math.MathHelper; 13 | import net.minecraft.world.GameRules; 14 | import net.minecraft.world.World; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Overwrite; 17 | 18 | @Mixin(EnderDragonEntity.class) 19 | public abstract class MixinEnderDragon extends MobEntity { 20 | private MixinEnderDragon(EntityType entityType, World world) { 21 | super(entityType, world); 22 | } 23 | 24 | /** 25 | * @author Ampflower 26 | * @reason Cancelling removeBlock when locked container, and moving the griefing check to before the entire loop. 27 | */ 28 | @Overwrite 29 | private boolean destroyBlocks(Box box) { 30 | int x1 = MathHelper.floor(box.minX), y1 = MathHelper.floor(box.minY), z1 = MathHelper.floor(box.minZ), 31 | x2 = MathHelper.floor(box.maxX), y2 = MathHelper.floor(box.maxY), z2 = MathHelper.floor(box.maxZ); 32 | // Moved grief to not call it a hundred times, it only needs to be called once. 33 | boolean grief = world.getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING), b1 = false, b2 = false; 34 | for (int x = x1; x <= x2; x++) 35 | for (int y = y1; y <= y2; y++) 36 | for (int z = z1; z <= z2; z++) { 37 | var pos = new BlockPos(x, y, z); 38 | var state = world.getBlockState(pos); 39 | if (!state.isAir() && state.getMaterial() != Material.FIRE) { 40 | if (grief && !state.isIn(BlockTags.DRAGON_IMMUNE) && (!state.hasBlockEntity() || Locking.canBreak((ILockable) world.getBlockEntity(pos), this))) { 41 | b2 = world.removeBlock(pos, false) || b2; 42 | } else { 43 | b1 = true; 44 | } 45 | } 46 | } 47 | if (b2) 48 | world.syncWorldEvent(2008, new BlockPos(x1 + random.nextInt(x2 - x1 + 1), y1 + random.nextInt(y2 - y1 + 1), z1 + random.nextInt(z2 - z1 + 1)), 0); 49 | return b1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/entities/MixinEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins.entities; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.util.math.BlockPos; 8 | import net.minecraft.world.BlockView; 9 | import net.minecraft.world.explosion.Explosion; 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 | @Mixin(Entity.class) 16 | public abstract class MixinEntity { 17 | 18 | @Inject(method = "canExplosionDestroyBlock(Lnet/minecraft/world/explosion/Explosion;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;F)Z", at = @At("HEAD"), cancellable = true) 19 | private void helium$canExplosionDestroyBlock(Explosion explosion, BlockView blockView, BlockPos pos, BlockState blockState, float power, CallbackInfoReturnable cbir) { 20 | if (blockState.hasBlockEntity() && !Locking.canBreak((ILockable) blockView.getBlockEntity(pos), (Entity) (Object) this)) 21 | cbir.setReturnValue(false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/entities/MixinPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins.entities; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.world.GameMode; 10 | import net.minecraft.world.World; 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.CallbackInfoReturnable; 15 | 16 | @Mixin(PlayerEntity.class) 17 | public abstract class MixinPlayerEntity extends LivingEntity { 18 | private MixinPlayerEntity(EntityType entityType, World world) { 19 | super(entityType, world); 20 | } 21 | 22 | @Inject(method = "isBlockBreakingRestricted(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/GameMode;)Z", at = @At("HEAD"), cancellable = true) 23 | private void helium$isBlockBreakingRestricted(World world, BlockPos pos, GameMode gameMode, CallbackInfoReturnable cbir) { 24 | if (!world.isClient && !Locking.canBreak((ILockable) world.getBlockEntity(pos), this)) 25 | cbir.setReturnValue(true); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ply-locking/src/main/java/gay/ampflower/plymouth/locking/mixins/entities/MixinTntMinecartEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.locking.mixins.entities; 2 | 3 | import gay.ampflower.plymouth.locking.ILockable; 4 | import gay.ampflower.plymouth.locking.Locking; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.entity.EntityType; 7 | import net.minecraft.entity.vehicle.AbstractMinecartEntity; 8 | import net.minecraft.entity.vehicle.TntMinecartEntity; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.world.BlockView; 11 | import net.minecraft.world.World; 12 | import net.minecraft.world.explosion.Explosion; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | @Mixin(TntMinecartEntity.class) 19 | public abstract class MixinTntMinecartEntity extends AbstractMinecartEntity { 20 | private MixinTntMinecartEntity(EntityType type, World world) { 21 | super(type, world); 22 | } 23 | 24 | @Inject(method = "canExplosionDestroyBlock(Lnet/minecraft/world/explosion/Explosion;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;F)Z", at = @At("HEAD"), cancellable = true) 25 | private void helium$canExplosionDestroyBlock(Explosion explosion, BlockView blockView, BlockPos pos, BlockState blockState, float power, CallbackInfoReturnable cbir) { 26 | if (blockState.hasBlockEntity() && !Locking.canBreak((ILockable) blockView.getBlockEntity(pos), this)) 27 | cbir.setReturnValue(false); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ply-locking/src/main/resources/assets/plymouth-locking/lang/en_us.json: -------------------------------------------------------------------------------- 1 | ../../../data/plymouth-database/lang/en_us.json -------------------------------------------------------------------------------- /ply-locking/src/main/resources/data/plymouth-database/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "plymouth.locking.removed": "Successfully removed %s from %s at %s.", 3 | "plymouth.locking.allowed": "Successfully allowed %s to %s at %s.", 4 | "plymouth.locking.modified": "Successfully modified permissions for %s on %s at %s.", 5 | "plymouth.locking.claimed": "Successfully claimed %s at %s.", 6 | "plymouth.locking.unclaimed": "Successfully unclaimed %s at %s.", 7 | "plymouth.locking.locked": "%s is claimed by %s.", 8 | "plymouth.locking.dump.basic": "Locked by %s:%s with permissions of %s (effective %s)", 9 | "plymouth.locking.dump.advanced.player": "Player %s -> %s", 10 | "plymouth.locking.dump.advanced.group": "Group %s -> %s", 11 | "commands.plymouth.locking.prompt": "Interact with a block to modify its permissions.", 12 | "commands.plymouth.locking.block.not_owned": "%s is not owned by anyone.", 13 | "commands.plymouth.locking.block.not_owner": "%s is not owned by you.", 14 | "commands.plymouth.locking.block.out_range": "%s is out of range for claiming." 15 | } -------------------------------------------------------------------------------- /ply-locking/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-locking", 4 | "version": "${version}", 5 | "name": "Plymouth: Locking", 6 | "description": "Lock your chests and trust a few with your items.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/Modflower/plymouth-fabric", 12 | "discord": "https://discord.gg/EmPS9y9" 13 | }, 14 | "environment": "*", 15 | "entrypoints": { 16 | "main": [ 17 | "gay.ampflower.plymouth.locking.Locking" 18 | ] 19 | }, 20 | "mixins": [ 21 | "plymouth-locking.mixin.json" 22 | ], 23 | "depends": { 24 | "minecraft": ">=${minecraft_required}", 25 | "plymouth-common": "^${project_version}", 26 | "fabric-command-api-v2": "*", 27 | "fabric-lifecycle-events-v1": "*", 28 | "fabric-permissions-api-v0": "*" 29 | }, 30 | "recommends": { 31 | "luckperms": "*" 32 | } 33 | } -------------------------------------------------------------------------------- /ply-locking/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 7, 4 | "description": "Plymouth: Locking" 5 | } 6 | } -------------------------------------------------------------------------------- /ply-locking/src/main/resources/plymouth-locking.mixin.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "gay.ampflower.plymouth.locking.mixins", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "MixinBlockEntity", 7 | "MixinExplosionBehavior", 8 | "MixinServerPlayerInteractionManager", 9 | "MixinServerWorld", 10 | "MixinWorld", 11 | "entities.MixinEnderDragon", 12 | "entities.MixinEntity", 13 | "entities.MixinPlayerEntity", 14 | "entities.MixinTntMinecartEntity" 15 | ], 16 | "injectors": { 17 | "defaultRequire": 1 18 | } 19 | } -------------------------------------------------------------------------------- /ply-tracker/README.md: -------------------------------------------------------------------------------- 1 | Plymouth Tracker 2 |
3 | 4 | # Plymouth: Tracker 5 | 6 | A world tracking engine making use of the database module, keeping track of blocks, deaths and inventories. 7 | 8 | ## Downloads 9 | 10 | You may download Locking from [Modrinth](https://modrinth.com/mod/plymouth-tracker) or 11 | from [GitHub Releases](https://github.com/Modflower/plymouth-fabric/releases). 12 | 13 | ## Usage 14 | 15 | Drop the mod along with Database and Common into the mods folder of your server then boot it up. Configuration of 16 | Database is required, please see [the setup guide](../ply-database/README.md#setup-postgresql--linux) for a quick 17 | overview of setting up. There is no other configuration options available at this time. 18 | 19 |
-------------------------------------------------------------------------------- /ply-tracker/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | `java-library` 4 | id("fabric-loom") 5 | `maven-publish` 6 | } 7 | 8 | val fabric_api_version: String by project 9 | val fabric_permissions_version: String by project 10 | 11 | repositories { 12 | maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } 13 | } 14 | 15 | dependencies { 16 | modImplementation(fabricApi.module("fabric-command-api-v1", fabric_api_version)) { include(this) } 17 | implementation(project(":ply-database", configuration = "namedElements")) 18 | implementation(project(":utilities")) { include(this) } 19 | implementation(project(":database")) 20 | // implementation(project(":commander")) { include(this) } 21 | modImplementation("me.lucko", "fabric-permissions-api", fabric_permissions_version) 22 | } -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/Tracker.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker; 2 | 3 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 4 | import net.minecraft.text.Text; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since 0.0.0 11 | */ 12 | public final class Tracker { 13 | public static final Logger logger = LoggerFactory.getLogger("Plymouth: Tracker"); 14 | 15 | public static final Text 16 | INSPECT_START = Text.translatable("commands.plymouth.tracker.inspect.start"), 17 | INSPECT_END = Text.translatable("commands.plymouth.tracker.inspect.end"); 18 | 19 | public static void init() { 20 | CommandRegistrationCallback.EVENT.register(TrackerCommand::register); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/TrackerInspectionManagerInjection.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker; 2 | 3 | import gay.ampflower.plymouth.common.InteractionManagerInjection; 4 | import gay.ampflower.plymouth.database.DatabaseHelper; 5 | import gay.ampflower.plymouth.database.records.BlockLookupRecord; 6 | import gay.ampflower.plymouth.database.records.InventoryLookupRecord; 7 | import net.minecraft.item.ItemStack; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.world.ServerWorld; 10 | import net.minecraft.util.ActionResult; 11 | import net.minecraft.util.Formatting; 12 | import net.minecraft.util.Hand; 13 | import net.minecraft.util.hit.BlockHitResult; 14 | import net.minecraft.util.math.BlockPos; 15 | import net.minecraft.util.math.Direction; 16 | 17 | /** 18 | * @author Ampflower 19 | * @since ${version} 20 | **/ 21 | public class TrackerInspectionManagerInjection implements InteractionManagerInjection { 22 | @Override 23 | public ActionResult onBreakBlock(ServerPlayerEntity player, final ServerWorld world, final BlockPos pos, Direction direction) { 24 | var lookup = new BlockLookupRecord(world, pos, 0); 25 | DatabaseHelper.database.queue(lookup); 26 | lookup.getFuture().thenAcceptAsync(l -> { 27 | for (var r : l) { 28 | player.sendMessage(r.toTextNoPosition(), false); 29 | } 30 | player.sendMessage(Text.translatable("commands.plymouth.tracker.lookup", lookup.toText(), "UTC").formatted(Formatting.DARK_GRAY), false); 31 | }).exceptionally(t -> { 32 | Tracker.logger.error("Exception on breakBlock", t); 33 | player.sendMessage(Text.literal(t.getLocalizedMessage()).formatted(Formatting.RED), false); 34 | return null; 35 | }); 36 | return ActionResult.CONSUME; 37 | } 38 | 39 | @Override 40 | public ActionResult onInteractBlock(ServerPlayerEntity player, ServerWorld world, ItemStack stack, Hand hand, BlockHitResult hitResult) { 41 | if (hand != Hand.MAIN_HAND) return ActionResult.CONSUME; 42 | var lookup = new InventoryLookupRecord(world, hitResult.getBlockPos(), 0); 43 | DatabaseHelper.database.queue(lookup); 44 | lookup.getFuture().thenAcceptAsync(l -> { 45 | for (var r : l) { 46 | player.sendMessage(r.toTextNoPosition(), false); 47 | } 48 | player.sendMessage(Text.translatable("commands.plymouth.tracker.lookup", lookup.toText(), "UTC").formatted(Formatting.DARK_GRAY), false); 49 | }).exceptionally(t -> { 50 | Tracker.logger.error("Exception on interactBlock", t); 51 | player.sendMessage(Text.literal(t.getLocalizedMessage()).formatted(Formatting.RED), false); 52 | return null; 53 | }); 54 | return ActionResult.CONSUME; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/glue/ITargetInjectable.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.glue; 2 | 3 | import gay.ampflower.plymouth.database.Target; 4 | 5 | /** 6 | * @author Ampflower 7 | * @since ${version} 8 | **/ 9 | public interface ITargetInjectable { 10 | void plymouth$injectTarget(Target target); 11 | } 12 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/glue/RespawnAnchorExplosionBehavior.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.glue; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.block.Blocks; 5 | import net.minecraft.fluid.FluidState; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.world.BlockView; 8 | import net.minecraft.world.World; 9 | import net.minecraft.world.explosion.Explosion; 10 | import net.minecraft.world.explosion.ExplosionBehavior; 11 | 12 | import java.util.Optional; 13 | 14 | /** 15 | * @author Ampflower 16 | * @vanilla-copy {@link net.minecraft.block.RespawnAnchorBlock#explode(BlockState, World, BlockPos)} 17 | * @since ${version} 18 | **/ 19 | public class RespawnAnchorExplosionBehavior extends ExplosionBehavior { 20 | private BlockPos explodedPos; 21 | private boolean bl2; 22 | 23 | public RespawnAnchorExplosionBehavior(BlockPos explodedPos, boolean bl2) { 24 | this.explodedPos = explodedPos; 25 | this.bl2 = bl2; 26 | } 27 | 28 | public Optional getBlastResistance(Explosion explosion, BlockView world, BlockPos pos, BlockState blockState, FluidState fluidState) { 29 | return pos.equals(explodedPos) && bl2 ? Optional.of(Blocks.WATER.getBlastResistance()) : super.getBlastResistance(explosion, world, pos, blockState, fluidState); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/AccessorDoubleInventory.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import net.minecraft.inventory.DoubleInventory; 4 | import net.minecraft.inventory.Inventory; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since ${version} 11 | **/ 12 | @Mixin(DoubleInventory.class) 13 | public interface AccessorDoubleInventory { 14 | @Accessor 15 | Inventory getFirst(); 16 | 17 | @Accessor 18 | Inventory getSecond(); 19 | } 20 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinAbstractDecorationEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.entity.Entity; 6 | import net.minecraft.entity.EntityType; 7 | import net.minecraft.entity.damage.DamageSource; 8 | import net.minecraft.entity.decoration.AbstractDecorationEntity; 9 | import net.minecraft.world.World; 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 | /** 16 | * @author Ampflower 17 | * @since ${version} 18 | **/ 19 | @Mixin(AbstractDecorationEntity.class) 20 | public abstract class MixinAbstractDecorationEntity extends Entity { 21 | public MixinAbstractDecorationEntity(EntityType type, World world) { 22 | super(type, world); 23 | } 24 | 25 | @Inject(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/decoration/AbstractDecorationEntity;kill()V")) 26 | private void plymouth$onKill(DamageSource source, float amount, CallbackInfoReturnable cir) { 27 | if (!world.isClient) DatabaseHelper.database.killEntity((Target) this, (Target) source); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinAnvilScreenHandler.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.entity.player.PlayerInventory; 8 | import net.minecraft.screen.AnvilScreenHandler; 9 | import net.minecraft.screen.ForgingScreenHandler; 10 | import net.minecraft.screen.ScreenHandlerContext; 11 | import net.minecraft.screen.ScreenHandlerType; 12 | import net.minecraft.server.world.ServerWorld; 13 | import net.minecraft.util.math.BlockPos; 14 | import net.minecraft.world.World; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 20 | 21 | @Mixin(AnvilScreenHandler.class) 22 | public abstract class MixinAnvilScreenHandler extends ForgingScreenHandler { 23 | public MixinAnvilScreenHandler(ScreenHandlerType type, int syncId, PlayerInventory playerInventory, ScreenHandlerContext context) { 24 | super(type, syncId, playerInventory, context); 25 | } 26 | 27 | /** 28 | * Track when the anvil is broken. 29 | * 30 | * @author Ampflower 31 | * @since 0.0.0 32 | */ 33 | // [RAW ASM - MUST CHECK] 34 | @SuppressWarnings("UnresolvedMixinReference") 35 | @Inject(method = "method_24922(Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V", 36 | at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;removeBlock(Lnet/minecraft/util/math/BlockPos;Z)Z", shift = At.Shift.AFTER), 37 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 38 | private static void plymouth$onTakeOutput$postBreakAnvil(PlayerEntity player, World world, BlockPos pos, CallbackInfo cbir, BlockState oldState) { 39 | if (world instanceof ServerWorld serverWorld) 40 | DatabaseHelper.database.breakBlock(serverWorld, pos, oldState, null, (Target) player); 41 | } 42 | 43 | /** 44 | * Track when the anvil is damaged. 45 | * 46 | * @author Ampflower 47 | * @since 0.0.0 48 | */ 49 | // [RAW ASM - MUST CHECK] 50 | @SuppressWarnings("UnresolvedMixinReference") 51 | @Inject(method = "method_24922(Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V", 52 | at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z", shift = At.Shift.AFTER), 53 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 54 | private static void plymouth$onTakeOutput$postDamageAnvil(PlayerEntity player, World world, BlockPos pos, CallbackInfo cbir, BlockState oldState, BlockState newState) { 55 | if (world instanceof ServerWorld serverWorld) 56 | DatabaseHelper.database.replaceBlock(serverWorld, pos, oldState, newState, (Target) player); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinArmorStandEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.entity.damage.DamageSource; 8 | import net.minecraft.entity.decoration.ArmorStandEntity; 9 | import net.minecraft.world.World; 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 | /** 16 | * @author Ampflower 17 | * @since ${version} 18 | **/ 19 | @Mixin(ArmorStandEntity.class) 20 | public abstract class MixinArmorStandEntity extends LivingEntity { 21 | protected MixinArmorStandEntity(EntityType entityType, World world) { 22 | super(entityType, world); 23 | } 24 | 25 | @Inject(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/decoration/ArmorStandEntity;kill()V")) 26 | private void plymouth$onKill(DamageSource source, float amount, CallbackInfoReturnable cir) { 27 | if (!world.isClient) DatabaseHelper.database.killEntity((Target) this, (Target) source); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinBlockItem.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.item.BlockItem; 8 | import net.minecraft.item.ItemPlacementContext; 9 | import net.minecraft.server.world.ServerWorld; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.world.World; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 17 | 18 | @Mixin(BlockItem.class) 19 | public class MixinBlockItem { 20 | @Inject(method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;", 21 | at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onPlaced(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;)V"), 22 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 23 | private void helium$place$onPlaced(ItemPlacementContext context, CallbackInfoReturnable cbir, ItemPlacementContext $i1, BlockState state, BlockPos pos, World world, PlayerEntity player) { 24 | if (world instanceof ServerWorld serverWorld) 25 | DatabaseHelper.database.placeBlock(serverWorld, pos, state, (Target) player); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinExplosion.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 4 | import gay.ampflower.plymouth.database.DatabaseHelper; 5 | import gay.ampflower.plymouth.database.Target; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.entity.LivingEntity; 9 | import net.minecraft.server.world.ServerWorld; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.world.World; 12 | import net.minecraft.world.explosion.Explosion; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Shadow; 17 | import org.spongepowered.asm.mixin.injection.At; 18 | import org.spongepowered.asm.mixin.injection.Inject; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 20 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 21 | 22 | import java.util.Iterator; 23 | 24 | /** 25 | * @author Ampflower 26 | * @since ${version} 27 | **/ 28 | @Mixin(Explosion.class) 29 | public abstract class MixinExplosion { 30 | @Shadow 31 | @Final 32 | private World world; 33 | 34 | @Shadow 35 | @Nullable 36 | public abstract LivingEntity getCausingEntity(); 37 | 38 | @Shadow 39 | @Final 40 | @Nullable 41 | private Entity entity; 42 | 43 | @SuppressWarnings("ConstantConditions") // the block entity should be not null if it has one. 44 | @Inject(method = "affectWorld", 45 | locals = LocalCapture.CAPTURE_FAILEXCEPTION, 46 | at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;shouldDropItemsOnExplosion(Lnet/minecraft/world/explosion/Explosion;)Z")) 47 | private void plymouth$affectWorld$onSetBlock(boolean flag, CallbackInfo cbi, boolean $2, ObjectArrayList $0, Iterator $1, BlockPos pos, BlockState old) { 48 | if (!(world instanceof ServerWorld serverWorld)) return; 49 | Entity e = getCausingEntity(); 50 | if (e == null) e = entity; 51 | DatabaseHelper.database.breakBlock(serverWorld, pos, old, old.hasBlockEntity() ? world.getBlockEntity(pos).createNbt() : null, (Target) e); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinFireCharge.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.block.Blocks; 6 | import net.minecraft.item.FireChargeItem; 7 | import net.minecraft.item.Item; 8 | import net.minecraft.item.ItemUsageContext; 9 | import net.minecraft.server.world.ServerWorld; 10 | import net.minecraft.util.ActionResult; 11 | import net.minecraft.util.math.BlockPos; 12 | import net.minecraft.world.World; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 18 | 19 | @Mixin(FireChargeItem.class) 20 | public class MixinFireCharge extends Item { 21 | public MixinFireCharge(Settings settings) { 22 | super(settings); 23 | } 24 | 25 | @Inject(method = "useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;", 26 | at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)Z", ordinal = 1), 27 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 28 | private void helium$useOnBlock$logUsage(ItemUsageContext iuc, CallbackInfoReturnable cbir, World world, BlockPos pos) { 29 | if (world instanceof ServerWorld serverWorld) 30 | DatabaseHelper.database.placeBlock(serverWorld, pos, Blocks.FIRE, (Target) iuc.getPlayer()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinFlintAndSteel.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.block.Blocks; 6 | import net.minecraft.entity.player.PlayerEntity; 7 | import net.minecraft.item.FlintAndSteelItem; 8 | import net.minecraft.item.Item; 9 | import net.minecraft.item.ItemUsageContext; 10 | import net.minecraft.server.world.ServerWorld; 11 | import net.minecraft.util.ActionResult; 12 | import net.minecraft.util.math.BlockPos; 13 | import net.minecraft.world.World; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 18 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 19 | 20 | @Mixin(FlintAndSteelItem.class) 21 | public class MixinFlintAndSteel extends Item { 22 | public MixinFlintAndSteel(Settings settings) { 23 | super(settings); 24 | } 25 | 26 | @Inject(method = "useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;", 27 | at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z", ordinal = 1), 28 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 29 | private void helium$useOnBlock$logUsage(ItemUsageContext iuc, CallbackInfoReturnable cbir, PlayerEntity player, World world, BlockPos $2, BlockPos pos) { 30 | if (world instanceof ServerWorld serverWorld) 31 | DatabaseHelper.database.placeBlock(serverWorld, pos, Blocks.FIRE, (Target) player); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinHybridLPEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.LivingEntity; 7 | import net.minecraft.entity.damage.DamageSource; 8 | import net.minecraft.entity.player.PlayerEntity; 9 | import net.minecraft.world.World; 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.CallbackInfo; 14 | 15 | @Mixin({PlayerEntity.class, LivingEntity.class}) 16 | public abstract class MixinHybridLPEntity extends Entity { 17 | public MixinHybridLPEntity(EntityType type, World world) { 18 | super(type, world); 19 | } 20 | 21 | @Inject(method = "applyDamage(Lnet/minecraft/entity/damage/DamageSource;F)V", 22 | at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/damage/DamageTracker;onDamage(Lnet/minecraft/entity/damage/DamageSource;FF)V"), require = 1) 23 | private void helium$onDamage(DamageSource source, float amount, CallbackInfo cbi) { 24 | if (!world.isClient) DatabaseHelper.database.hurtEntity((LivingEntity) (Object) this, amount, source); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.entity.EntityType; 6 | import net.minecraft.entity.ItemEntity; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.item.ItemStack; 9 | import net.minecraft.world.World; 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 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 15 | 16 | /** 17 | * @author Ampflower 18 | * @since Jan. 02, 2021 @ 15:43 19 | **/ 20 | @Mixin(PlayerEntity.class) 21 | public abstract class MixinPlayerEntity extends Entity { 22 | public MixinPlayerEntity(EntityType type, World world) { 23 | super(type, world); 24 | } 25 | 26 | @Inject(method = "dropItem(Lnet/minecraft/item/ItemStack;ZZ)Lnet/minecraft/entity/ItemEntity;", at = @At(value = "RETURN", ordinal = 1), locals = LocalCapture.CAPTURE_FAILHARD) 27 | private void helium$onDropItem(ItemStack stack, boolean throwRandomly, boolean retainOwnership, CallbackInfoReturnable cir, double ignored$0, ItemEntity entity) { 28 | DatabaseHelper.database.createEntity(entity, this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/MixinPlayerInteractionManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins; 2 | 3 | import gay.ampflower.plymouth.database.DatabaseHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import net.minecraft.block.Block; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.block.entity.BlockEntity; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.server.network.ServerPlayerInteractionManager; 10 | import net.minecraft.server.world.ServerWorld; 11 | import net.minecraft.util.math.BlockPos; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 18 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 19 | 20 | @Mixin(ServerPlayerInteractionManager.class) 21 | public class MixinPlayerInteractionManager { 22 | @Shadow 23 | protected ServerWorld world; 24 | 25 | @Final 26 | @Shadow 27 | protected ServerPlayerEntity player; 28 | 29 | @Inject(method = "tryBreakBlock(Lnet/minecraft/util/math/BlockPos;)Z", 30 | at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onBroken(Lnet/minecraft/world/WorldAccess;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V"), 31 | locals = LocalCapture.CAPTURE_FAILEXCEPTION) 32 | private void plymouth$tryBreakBlock$onBlockBroken(BlockPos pos, CallbackInfoReturnable cbir, BlockState state, BlockEntity entity, Block block) { 33 | DatabaseHelper.database.breakBlock(world, pos, state, entity == null ? null : entity.createNbt(), (Target) player); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/explosions/MixinBedBlock.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.explosions; 2 | 3 | import net.minecraft.block.BedBlock; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.entity.Entity; 6 | import net.minecraft.entity.damage.DamageSource; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.world.World; 10 | import net.minecraft.world.explosion.Explosion; 11 | import net.minecraft.world.explosion.ExplosionBehavior; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Redirect; 15 | 16 | /** 17 | * @author Ampflower 18 | * @since ${version} 19 | **/ 20 | @Mixin(BedBlock.class) 21 | public class MixinBedBlock { 22 | @Redirect(method = "onUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;createExplosion(Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/damage/DamageSource;Lnet/minecraft/world/explosion/ExplosionBehavior;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)Lnet/minecraft/world/explosion/Explosion;")) 23 | private Explosion plymouth$redirect$world$createExplosion(World world, Entity entity, DamageSource damageSource, ExplosionBehavior behavior, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, 24 | BlockState $0, World $1, BlockPos $2, PlayerEntity player) { 25 | return world.createExplosion(player, damageSource, behavior, x, y, z, power, createFire, destructionType); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/explosions/MixinEndCrystalEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.explosions; 2 | 3 | import gay.ampflower.plymouth.common.UUIDHelper; 4 | import net.minecraft.entity.Entity; 5 | import net.minecraft.entity.damage.DamageSource; 6 | import net.minecraft.entity.decoration.EndCrystalEntity; 7 | import net.minecraft.world.World; 8 | import net.minecraft.world.explosion.Explosion; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | 13 | /** 14 | * @author Ampflower 15 | * @since ${version} 16 | **/ 17 | @Mixin(EndCrystalEntity.class) 18 | public class MixinEndCrystalEntity { 19 | @Redirect(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;createExplosion(Lnet/minecraft/entity/Entity;DDDFLnet/minecraft/world/explosion/Explosion$DestructionType;)Lnet/minecraft/world/explosion/Explosion;")) 20 | private Explosion plymouth$redirect$world$createExplosion(World world, Entity entity, double x, double y, double z, float power, Explosion.DestructionType destructionType, DamageSource source) { 21 | return world.createExplosion(UUIDHelper.getEntity(source), x, y, z, power, destructionType); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/explosions/MixinFireballEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.explosions; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.entity.EntityType; 5 | import net.minecraft.entity.projectile.FireballEntity; 6 | import net.minecraft.world.World; 7 | import net.minecraft.world.explosion.Explosion; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | /** 13 | * @author Ampflower 14 | * @since ${version} 15 | **/ 16 | @Mixin(FireballEntity.class) 17 | public abstract class MixinFireballEntity extends Entity { 18 | public MixinFireballEntity(EntityType type, World world) { 19 | super(type, world); 20 | } 21 | 22 | /** 23 | * Fixes critical crash with null being passed for entity. 24 | */ 25 | @Redirect(method = "onCollision", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;createExplosion(Lnet/minecraft/entity/Entity;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)Lnet/minecraft/world/explosion/Explosion;")) 26 | private Explosion plymouth$redirect$world$createExplosion(World world, Entity entity, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType) { 27 | return world.createExplosion(this, x, y, z, power, createFire, destructionType); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/explosions/MixinRespawnAnchorBlock.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.explosions; 2 | 3 | import gay.ampflower.plymouth.tracker.glue.RespawnAnchorExplosionBehavior; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.block.RespawnAnchorBlock; 6 | import net.minecraft.entity.damage.DamageSource; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.tag.FluidTags; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.util.math.Direction; 11 | import net.minecraft.world.World; 12 | import net.minecraft.world.explosion.Explosion; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | /** 19 | * @author Ampflower 20 | * @since ${version} 21 | **/ 22 | @Mixin(RespawnAnchorBlock.class) 23 | public abstract class MixinRespawnAnchorBlock { 24 | @Shadow 25 | private static boolean hasStillWater(BlockPos pos, World world) { 26 | return false; 27 | } 28 | 29 | /** 30 | * @vanilla-copy 31 | */ 32 | @Redirect(method = "onUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/RespawnAnchorBlock;explode(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V")) 33 | private void plymouth$redirect$self$explode(RespawnAnchorBlock respawnAnchorBlock, BlockState state, World world, BlockPos explodedPos, BlockState $0, World $1, BlockPos $2, PlayerEntity player) { 34 | world.removeBlock(explodedPos, false); 35 | final boolean bl2 = world.getFluidState(explodedPos.up()).isIn(FluidTags.WATER) || Direction.Type.HORIZONTAL.stream().map(explodedPos::offset).anyMatch(pos -> hasStillWater(pos, world)); 36 | world.createExplosion(player, DamageSource.badRespawnPoint(), new RespawnAnchorExplosionBehavior(explodedPos, bl2), explodedPos.getX() + 0.5D, explodedPos.getY() + 0.5D, explodedPos.getZ() + 0.5D, 5.0F, true, Explosion.DestructionType.DESTROY); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinBlockEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.common.UUIDHelper; 4 | import gay.ampflower.plymouth.database.Target; 5 | import gay.ampflower.plymouth.database.records.TargetRecord; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.block.entity.BlockEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.util.registry.Registry; 10 | import net.minecraft.world.World; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.Unique; 15 | 16 | import java.util.UUID; 17 | 18 | /** 19 | * @author Ampflower 20 | * @since ${version} 21 | **/ 22 | @Mixin(BlockEntity.class) 23 | public abstract class MixinBlockEntity implements Target { 24 | @Shadow 25 | @Nullable 26 | protected World world; 27 | @Shadow 28 | protected BlockPos pos; 29 | 30 | @Shadow 31 | public abstract BlockState getCachedState(); 32 | 33 | @Unique 34 | private TargetRecord record; 35 | 36 | @Override 37 | public boolean ply$isBlock() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public TargetRecord plymouth$toRecord() { 43 | return record == null ? record = Target.super.plymouth$toRecord() : record; 44 | } 45 | 46 | @Override 47 | public World ply$world() { 48 | return world; 49 | } 50 | 51 | @Override 52 | public BlockPos ply$pos3i() { 53 | return pos; 54 | } 55 | 56 | @Override 57 | public String ply$name() { 58 | return Registry.BLOCK.getId(getCachedState().getBlock()).toString(); 59 | } 60 | 61 | @Override 62 | public UUID ply$userId() { 63 | return UUIDHelper.getUUID(getCachedState().getBlock()); 64 | } 65 | 66 | @Override 67 | public UUID ply$entityId() { 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinDamageSource.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.common.UUIDHelper; 4 | import gay.ampflower.plymouth.database.DatabaseHelper; 5 | import gay.ampflower.plymouth.database.Target; 6 | import net.minecraft.entity.damage.DamageSource; 7 | import net.minecraft.entity.player.PlayerEntity; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.util.math.Vec3d; 10 | import net.minecraft.world.World; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | 13 | import java.util.UUID; 14 | 15 | /** 16 | * @author Ampflower 17 | * @since ${version} 18 | **/ 19 | @Mixin(DamageSource.class) 20 | public class MixinDamageSource implements Target { 21 | @Override 22 | public World ply$world() { 23 | var cause = UUIDHelper.getEntity((DamageSource) (Object) this); 24 | return cause != null ? cause.world : null; 25 | } 26 | 27 | @Override 28 | public BlockPos ply$pos3i() { 29 | var cause = UUIDHelper.getEntity((DamageSource) (Object) this); 30 | return cause != null ? cause.getBlockPos() : null; 31 | } 32 | 33 | @Override 34 | public Vec3d ply$pos3d() { 35 | var cause = UUIDHelper.getEntity((DamageSource) (Object) this); 36 | return cause != null ? cause.getPos() : null; 37 | } 38 | 39 | @Override 40 | public String ply$name() { 41 | return DatabaseHelper.getName((DamageSource) (Object) this); 42 | } 43 | 44 | @Override 45 | public UUID ply$userId() { 46 | return UUIDHelper.getUUID((DamageSource) (Object) this); 47 | } 48 | 49 | @Override 50 | public UUID ply$entityId() { 51 | var cause = UUIDHelper.getEntity((DamageSource) (Object) this); 52 | return cause instanceof PlayerEntity ? null : cause.getUuid(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinEnderInventory.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.database.Target; 4 | import gay.ampflower.plymouth.database.records.TargetRecord; 5 | import gay.ampflower.plymouth.tracker.glue.ITargetInjectable; 6 | import net.minecraft.inventory.EnderChestInventory; 7 | import net.minecraft.util.math.BlockPos; 8 | import net.minecraft.util.math.Vec3d; 9 | import net.minecraft.world.World; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | 14 | import java.util.UUID; 15 | 16 | /** 17 | * @author Ampflower 18 | * @since ${version} 19 | **/ 20 | @Mixin(EnderChestInventory.class) 21 | public class MixinEnderInventory implements Target, ITargetInjectable { 22 | @Unique 23 | public Target player; 24 | 25 | public void plymouth$injectTarget(Target player) { 26 | this.player = player; 27 | } 28 | 29 | @Override 30 | public TargetRecord plymouth$toRecord() { 31 | return player.plymouth$toRecord(); 32 | } 33 | 34 | @Override 35 | public boolean ply$isBlock() { 36 | return player.ply$isBlock(); 37 | } 38 | 39 | @Override 40 | public World ply$world() { 41 | return player.ply$world(); 42 | } 43 | 44 | @Override 45 | public World ply$blockWorld() { 46 | return player.ply$blockWorld(); 47 | } 48 | 49 | @Override 50 | public BlockPos ply$pos3i() { 51 | return player.ply$pos3i(); 52 | } 53 | 54 | @Override 55 | public BlockPos ply$blockPos3i() { 56 | return player.ply$blockPos3i(); 57 | } 58 | 59 | @Override 60 | public Vec3d ply$pos3d() { 61 | return player.ply$pos3d(); 62 | } 63 | 64 | @Override 65 | public String ply$name() { 66 | return player.ply$name(); 67 | } 68 | 69 | @Override 70 | public UUID ply$userId() { 71 | return player.ply$userId(); 72 | } 73 | 74 | @Override 75 | public UUID ply$entityId() { 76 | return player.ply$entityId(); 77 | } 78 | 79 | @Override 80 | public boolean ply$targetMatches(@Nullable Target other) { 81 | return player.ply$targetMatches(other); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.common.UUIDHelper; 4 | import gay.ampflower.plymouth.database.DatabaseHelper; 5 | import gay.ampflower.plymouth.database.Target; 6 | import gay.ampflower.plymouth.database.records.TargetRecord; 7 | import net.minecraft.entity.Entity; 8 | import net.minecraft.entity.player.PlayerEntity; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.util.math.Vec3d; 11 | import net.minecraft.world.World; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.Unique; 15 | 16 | import java.util.UUID; 17 | 18 | /** 19 | * @author Ampflower 20 | * @since ${version} 21 | **/ 22 | @Mixin(Entity.class) 23 | public class MixinEntity implements Target { 24 | @Shadow 25 | protected UUID uuid; 26 | @Shadow 27 | public World world; 28 | @Shadow 29 | private BlockPos blockPos; 30 | @Shadow 31 | private Vec3d pos; 32 | @Unique 33 | private TargetRecord record; 34 | 35 | @Override 36 | public TargetRecord plymouth$toRecord() { 37 | return record == null ? record = TargetRecord.ofEntityNoPosition((Entity) (Object) this) : record; 38 | } 39 | 40 | @Override 41 | public World ply$world() { 42 | return world; 43 | } 44 | 45 | @Override 46 | public BlockPos ply$pos3i() { 47 | return blockPos; 48 | } 49 | 50 | @Override 51 | public Vec3d ply$pos3d() { 52 | return pos; 53 | } 54 | 55 | @Override 56 | public String ply$name() { 57 | return DatabaseHelper.getName((Entity) (Object) this); 58 | } 59 | 60 | @Override 61 | public UUID ply$userId() { 62 | return UUIDHelper.getUUID((Entity) (Object) this); 63 | } 64 | 65 | @SuppressWarnings("ConstantConditions") 66 | @Override 67 | public UUID ply$entityId() { 68 | Object self = this; 69 | return self instanceof PlayerEntity ? null : uuid; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.database.Target; 4 | import gay.ampflower.plymouth.tracker.glue.ITargetInjectable; 5 | import net.minecraft.entity.player.PlayerEntity; 6 | import net.minecraft.inventory.EnderChestInventory; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 12 | 13 | /** 14 | * @author Ampflower 15 | * @since ${version} 16 | **/ 17 | @Mixin(PlayerEntity.class) 18 | public class MixinPlayerEntity { 19 | @Shadow 20 | protected EnderChestInventory enderChestInventory; 21 | 22 | @Inject(method = "", at = @At(value = "RETURN")) 23 | private void plymouth$injectPlayerIntoEnderChest(CallbackInfo ci) { 24 | ((ITargetInjectable) enderChestInventory).plymouth$injectTarget((Target) this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ply-tracker/src/main/java/gay/ampflower/plymouth/tracker/mixins/targets/MixinPlayerInventory.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.plymouth.tracker.mixins.targets; 2 | 3 | import gay.ampflower.plymouth.database.Target; 4 | import gay.ampflower.plymouth.database.records.TargetRecord; 5 | import net.minecraft.entity.player.PlayerEntity; 6 | import net.minecraft.entity.player.PlayerInventory; 7 | import net.minecraft.util.math.BlockPos; 8 | import net.minecraft.util.math.Vec3d; 9 | import net.minecraft.world.World; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | 15 | import java.util.UUID; 16 | 17 | /** 18 | * Allows tracking of player inventories through invsee and similar commands. 19 | * 20 | * @author Ampflower 21 | * @since ${version} 22 | **/ 23 | @Mixin(PlayerInventory.class) 24 | public class MixinPlayerInventory implements Target { 25 | @Shadow 26 | @Final 27 | public PlayerEntity player; 28 | 29 | @Override 30 | public TargetRecord plymouth$toRecord() { 31 | return ((Target) player).plymouth$toRecord(); 32 | } 33 | 34 | @Override 35 | public boolean ply$isBlock() { 36 | return ((Target) player).ply$isBlock(); 37 | } 38 | 39 | @Override 40 | public World ply$world() { 41 | return ((Target) player).ply$world(); 42 | } 43 | 44 | @Override 45 | public World ply$blockWorld() { 46 | return ((Target) player).ply$blockWorld(); 47 | } 48 | 49 | @Override 50 | public BlockPos ply$pos3i() { 51 | return ((Target) player).ply$pos3i(); 52 | } 53 | 54 | @Override 55 | public BlockPos ply$blockPos3i() { 56 | return ((Target) player).ply$blockPos3i(); 57 | } 58 | 59 | @Override 60 | public Vec3d ply$pos3d() { 61 | return ((Target) player).ply$pos3d(); 62 | } 63 | 64 | @Override 65 | public String ply$name() { 66 | return ((Target) player).ply$name(); 67 | } 68 | 69 | @Override 70 | public UUID ply$userId() { 71 | return ((Target) player).ply$userId(); 72 | } 73 | 74 | @Override 75 | public UUID ply$entityId() { 76 | return ((Target) player).ply$entityId(); 77 | } 78 | 79 | @Override 80 | public boolean ply$targetMatches(@Nullable Target other) { 81 | return ((Target) player).ply$targetMatches(other); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ply-tracker/src/main/resources/assets/plymouth-tracker/lang/en_us.json: -------------------------------------------------------------------------------- 1 | ../../../data/plymouth-database/lang/en_us.json -------------------------------------------------------------------------------- /ply-tracker/src/main/resources/data/plymouth-database/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands.plymouth.tracker.inspect.start": "You're now inspecting.\n- Punch a block to inspect placement.\n- Use a block to inspect inventories.", 3 | "commands.plymouth.tracker.inspect.end": "You're no longer inspecting.", 4 | "commands.plymouth.tracker.lookup": "Lookup %s. Time in %s.", 5 | "commands.plymouth.tracker.invalid": "Invalid argument %s.", 6 | "commands.plymouth.tracker.invalid.record": "Define if lookup should be under block, death or inventory." 7 | } -------------------------------------------------------------------------------- /ply-tracker/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth-tracker", 4 | "version": "${version}", 5 | "name": "Plymouth: Tracker", 6 | "description": "Track your world changes.", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "contact": { 11 | "sources": "https://github.com/Modflower/plymouth-fabric", 12 | "discord": "https://discord.gg/EmPS9y9" 13 | }, 14 | "environment": "*", 15 | "entrypoints": { 16 | "main": [ 17 | "gay.ampflower.plymouth.tracker.Tracker::init" 18 | ] 19 | }, 20 | "mixins": [ 21 | "plymouth-tracker.mixins.json" 22 | ], 23 | "depends": { 24 | "minecraft": ">=${minecraft_required}", 25 | "fabric-permissions-api-v0": "*", 26 | "plymouth-common": "^${project_version}", 27 | "plymouth-database": "^${project_version}" 28 | }, 29 | "recommends": { 30 | "luckperms": "*" 31 | } 32 | } -------------------------------------------------------------------------------- /ply-tracker/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 7, 4 | "description": "Plymouth: Tracker" 5 | } 6 | } -------------------------------------------------------------------------------- /ply-tracker/src/main/resources/plymouth-tracker.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "gay.ampflower.plymouth.tracker.mixins", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "AccessorDoubleInventory", 7 | "MixinAbstractDecorationEntity", 8 | "MixinAnvilScreenHandler", 9 | "MixinArmorStandEntity", 10 | "MixinBlockItem", 11 | "MixinExplosion", 12 | "MixinFireCharge", 13 | "MixinFlintAndSteel", 14 | "MixinHybridLPEntity", 15 | "MixinPlayerInteractionManager", 16 | "ScreenHandlerMixin", 17 | "explosions.MixinBedBlock", 18 | "explosions.MixinEndCrystalEntity", 19 | "explosions.MixinFireballEntity", 20 | "explosions.MixinRespawnAnchorBlock", 21 | "targets.MixinBlockEntity", 22 | "targets.MixinDamageSource", 23 | "targets.MixinEnderInventory", 24 | "targets.MixinEntity", 25 | "targets.MixinPlayerEntity", 26 | "targets.MixinPlayerInventory" 27 | ], 28 | "injectors": { 29 | "defaultRequire": 1 30 | } 31 | } -------------------------------------------------------------------------------- /ply-utilities/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.URI 2 | 3 | plugins { 4 | java 5 | `java-library` 6 | id("fabric-loom") 7 | `maven-publish` 8 | } 9 | 10 | val fabric_api_version: String by project 11 | val fabric_permissions_version: String by project 12 | 13 | repositories { 14 | mavenCentral() 15 | maven { url = URI.create("https://oss.sonatype.org/content/repositories/snapshots") } 16 | } 17 | 18 | dependencies { 19 | modImplementation("me.lucko", "fabric-permissions-api", fabric_permissions_version) 20 | modRuntimeOnly(fabricApi.module("fabric-resource-loader-v0", fabric_api_version)) 21 | } 22 | -------------------------------------------------------------------------------- /ply-utilities/src/main/java/gay/ampflower/helium/Helium.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.helium; 2 | 3 | import com.mojang.datafixers.DataFixer; 4 | import net.minecraft.block.Blocks; 5 | import net.minecraft.datafixer.DataFixTypes; 6 | import net.minecraft.nbt.NbtCompound; 7 | import net.minecraft.nbt.NbtIo; 8 | import net.minecraft.text.Style; 9 | import net.minecraft.text.Text; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.DataInputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.PushbackInputStream; 17 | 18 | import static net.minecraft.text.Text.*; 19 | import static net.minecraft.util.Formatting.*; 20 | 21 | /** 22 | * This class is a late-init-type class. It's expecting that 23 | * by the time that it's called, all blocks would have been 24 | * registered. 25 | * 26 | * @author Ampflower 27 | * @since ${version} 28 | */ 29 | public class Helium { 30 | public static final Style LINK = Style.EMPTY.withFormatting(AQUA, UNDERLINE); 31 | public static final Text 32 | SEE_LOGS = literal("See the server logs for more information.").formatted(ITALIC), 33 | DID_YOU_MEAN = translatable("plymouth.dym", keybind("key.inventory").formatted(AQUA)) 34 | .formatted(ITALIC, RED), 35 | ENDER_CHEST = translatable(Blocks.ENDER_CHEST.getTranslationKey()).formatted(DARK_PURPLE); 36 | public static final Logger logger = LoggerFactory.getLogger("Plymouth"); 37 | 38 | /** 39 | * [vanilla-copy] {@link net.minecraft.world.PersistentStateManager#readNbt(String, int)} 40 | * 41 | * @param stream Stream to read NBT data from. 42 | * @param dfu Server's DataFixer 43 | * @param dataVersion The data version to update to. 44 | * @return The NBT data, datafixed if necessary. 45 | */ 46 | public static NbtCompound readTag(InputStream stream, DataFixer dfu, int dataVersion) throws IOException { 47 | try (var inputStream = stream; var pushback = new PushbackInputStream(inputStream, 2)) { 48 | NbtCompound tag; 49 | if (isCompressed(pushback)) { 50 | tag = NbtIo.readCompressed(pushback); 51 | } else { 52 | try (var dataInput = new DataInputStream(pushback)) { 53 | tag = NbtIo.read(dataInput); 54 | } 55 | } 56 | int i = tag.contains("DataVersion", 99) ? tag.getInt("DataVersion") : 1343; 57 | return DataFixTypes.SAVED_DATA.update(dfu, tag, i, dataVersion); 58 | } 59 | } 60 | 61 | /** 62 | * [vanilla-copy] {@link net.minecraft.world.PersistentStateManager#isCompressed(PushbackInputStream)} 63 | */ 64 | public static boolean isCompressed(PushbackInputStream pushback) throws IOException { 65 | byte[] bytes = new byte[2]; 66 | int i; 67 | boolean r = ((i = pushback.read(bytes, 0, 2)) == 2 && ((bytes[1] & 255) << 8 | bytes[0] & 255) == 35615); 68 | if (i != 0) pushback.unread(bytes, 0, i); 69 | return r; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ply-utilities/src/main/java/gay/ampflower/helium/commands/InventoryLookupCommand.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.helium.commands; 2 | 3 | import com.mojang.brigadier.Command; 4 | import com.mojang.brigadier.CommandDispatcher; 5 | import me.lucko.fabric.api.permissions.v0.Permissions; 6 | import gay.ampflower.helium.Helium; 7 | import gay.ampflower.helium.mixins.AccessorPlayerEntity; 8 | import net.minecraft.entity.player.PlayerEntity; 9 | import net.minecraft.screen.GenericContainerScreenHandler; 10 | import net.minecraft.screen.ScreenHandlerType; 11 | import net.minecraft.screen.SimpleNamedScreenHandlerFactory; 12 | import net.minecraft.server.command.ServerCommandSource; 13 | 14 | import java.util.function.Predicate; 15 | 16 | import static net.minecraft.command.argument.EntityArgumentType.getPlayer; 17 | import static net.minecraft.command.argument.EntityArgumentType.player; 18 | import static net.minecraft.server.command.CommandManager.argument; 19 | import static net.minecraft.server.command.CommandManager.literal; 20 | 21 | /** 22 | * Player inventory lookup. Does not do offline players, only those that are online at the time. 23 | * 24 | * @author Ampflower 25 | * @since 0.0.0 26 | **/ 27 | public class InventoryLookupCommand { 28 | public static final Predicate 29 | REQUIRE_INVSEE_PERMISSION = Permissions.require("helium.admin.moderation.invsee", 3), 30 | REQUIRE_ENDSEE_PERMISSION = Permissions.require("helium.admin.moderation.endsee", 3); 31 | 32 | public static void register(CommandDispatcher dispatcher) { 33 | var e0 = argument("target", player()) 34 | .requires(REQUIRE_ENDSEE_PERMISSION).executes(s -> { 35 | var p = getPlayer(s, "target"); 36 | var sp = s.getSource().getPlayerOrThrow(); 37 | sp.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, pi, pe) -> 38 | GenericContainerScreenHandler.createGeneric9x3(i, pi, p.getEnderChestInventory()), 39 | p.getDisplayName().copyContentOnly().append(" - ").append(Helium.ENDER_CHEST))); 40 | return Command.SINGLE_SUCCESS; 41 | }); 42 | 43 | var p0 = argument("target", player()).requires(REQUIRE_INVSEE_PERMISSION).executes(s -> { 44 | var p = getPlayer(s, "target"); 45 | var sp = s.getSource().getPlayerOrThrow(); 46 | if (sp.equals(p)) { 47 | sp.sendMessage(Helium.DID_YOU_MEAN); 48 | } else { 49 | // TODO: Replace with a better screen handler that accounts for hidden player inventory. 50 | sp.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, pi, pe) -> 51 | new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X4, i, pi, ((AccessorPlayerEntity) p).getInventory(), 4) { 52 | @Override 53 | public boolean canUse(PlayerEntity player) { 54 | return true; 55 | } 56 | }, p.getDisplayName())); 57 | } 58 | return Command.SINGLE_SUCCESS; 59 | }); 60 | 61 | var i1 = dispatcher.register(literal("inventory") 62 | .then(literal("ender").then(e0)) 63 | .then(literal("e").then(e0)) 64 | .then(literal("player").then(p0)) 65 | .then(literal("p").then(p0)) 66 | .then(p0)); 67 | dispatcher.register(literal("inv").redirect(i1)); 68 | dispatcher.register(literal("invsee").redirect(i1)); 69 | dispatcher.register(literal("endsee").then(e0)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ply-utilities/src/main/java/gay/ampflower/helium/mixins/AccessorMapState.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.helium.mixins; 2 | 3 | import net.minecraft.item.map.MapState; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author Ampflower 12 | * @since ${version} 13 | **/ 14 | @Mixin(MapState.class) 15 | public interface AccessorMapState { 16 | @Accessor 17 | List getUpdateTrackers(); 18 | 19 | @Invoker 20 | void callMarkDirty(int x, int y); 21 | } 22 | -------------------------------------------------------------------------------- /ply-utilities/src/main/java/gay/ampflower/helium/mixins/AccessorPlayerEntity.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.helium.mixins; 2 | 3 | import net.minecraft.entity.player.PlayerEntity; 4 | import net.minecraft.entity.player.PlayerInventory; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | /** 9 | * @author Ampflower 10 | * @since ${version} 11 | **/ 12 | @Mixin(PlayerEntity.class) 13 | public interface AccessorPlayerEntity { 14 | @Accessor 15 | PlayerInventory getInventory(); 16 | } 17 | -------------------------------------------------------------------------------- /ply-utilities/src/main/java/gay/ampflower/helium/mixins/MixinCommandManager.java: -------------------------------------------------------------------------------- 1 | package gay.ampflower.helium.mixins; 2 | 3 | import com.mojang.brigadier.CommandDispatcher; 4 | import com.mojang.brigadier.ParseResults; 5 | import gay.ampflower.helium.Helium; 6 | import gay.ampflower.helium.commands.HotspotCommand; 7 | import gay.ampflower.helium.commands.InventoryLookupCommand; 8 | import gay.ampflower.helium.commands.MappingCommand; 9 | import net.minecraft.command.CommandRegistryAccess; 10 | import net.minecraft.server.command.CommandManager; 11 | import net.minecraft.server.command.ServerCommandSource; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Inject; 17 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 19 | 20 | // TODO: Use Fabric API 21 | @Mixin(CommandManager.class) 22 | public abstract class MixinCommandManager { 23 | @Shadow 24 | @Final 25 | private CommandDispatcher dispatcher; 26 | 27 | @Inject(method = "", at = @At("TAIL")) 28 | public void helium$registerCommands(CommandManager.RegistrationEnvironment environment, CommandRegistryAccess commandRegistryAccess, CallbackInfo ci) { 29 | InventoryLookupCommand.register(dispatcher); 30 | HotspotCommand.register(dispatcher); 31 | MappingCommand.register(dispatcher); 32 | } 33 | 34 | @Inject(method = "execute(Lcom/mojang/brigadier/ParseResults;Ljava/lang/String;)I", at = @At("HEAD")) 35 | public void helium$execute$logCommandExecution(ParseResults parseResults, String command, CallbackInfoReturnable cir) { 36 | Helium.logger.info("{} has executed the following command: {}", parseResults.getContext().getSource().getName(), command); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/assets/plymouth-miscellaneous/lang/en_us.json: -------------------------------------------------------------------------------- 1 | ../../../data/plymouth-miscellaneous/lang/en_us.json -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/data/plymouth-miscellaneous/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "plymouth.map.fail.download": "Failed to download %s: %s", 3 | "plymouth.map.fail.parse": "Failed to parse %s: %s", 4 | "plymouth.map.deploy": "Deployed %s to map %s.", 5 | "plymouth.map.download": "Downloading %s for map %s.", 6 | "plymouth.hotspot.result": "%s @ %s, %s, %s", 7 | "plymouth.dym": "Did you mean %s?" 8 | } -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "plymouth", 4 | "version": "${version}", 5 | "name": "Plymouth: Utilities", 6 | "description": "Miscellaneous server-sided utilities", 7 | "authors": [ 8 | "Ampflower" 9 | ], 10 | "icon": "pack.png", 11 | "contact": { 12 | "sources": "https://github.com/Modflower/plymouth-fabric", 13 | "discord": "https://discord.gg/EmPS9y9" 14 | }, 15 | "license": [ 16 | "MPL-2.0" 17 | ], 18 | "environment": "*", 19 | "entrypoints": {}, 20 | "mixins": [ 21 | "helium.mixins.json" 22 | ], 23 | "depends": { 24 | "minecraft": ">=${minecraft_required}", 25 | "fabric-permissions-api-v0": "*" 26 | }, 27 | "recommends": { 28 | "luckperms": "*" 29 | } 30 | } -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/helium.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "gay.ampflower.helium.mixins", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "AccessorMapState", 7 | "AccessorPlayerEntity", 8 | "MixinCommandManager" 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | } 13 | } -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 7, 4 | "description": "Plymouth: Miscellaneous" 5 | } 6 | } -------------------------------------------------------------------------------- /ply-utilities/src/main/resources/pack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modflower/plymouth-fabric/fe0987dac6e90b0c379d5ff93afd06a53fa0a49a/ply-utilities/src/main/resources/pack.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "plymouth" 2 | 3 | pluginManagement { 4 | repositories { 5 | maven { 6 | name = "Fabric" 7 | url = uri("https://maven.fabricmc.net/") 8 | } 9 | maven { 10 | name = "Quilt" 11 | url = uri("https://maven.quiltmc.org/repository/release/") 12 | content { 13 | includeGroup("org.quiltmc") 14 | } 15 | } 16 | maven { 17 | name = "The Glitch" 18 | url = uri("https://maven.ampflower.gay/") 19 | content { 20 | includeModule("gay.ampflower", "plymouth-loom") 21 | includeModule("plymouth-loom", "plymouth-loom.gradle.plugin") 22 | } 23 | } 24 | gradlePluginPortal() 25 | } 26 | plugins { 27 | // id("plymouth-loom") version System.getProperty("loom_version")!! 28 | id("fabric-loom") version System.getProperty("loom_version")!! 29 | id("com.diffplug.spotless") version System.getProperty("spotless_version")!! 30 | id("com.modrinth.minotaur") version System.getProperty("minotaur_version")!! 31 | } 32 | } 33 | 34 | include("utilities", "ply-common", "ply-anti-xray", "ply-locking", "ply-debug", "ply-utilities") 35 | 36 | // If you want to build Tracker, uncomment the following line: 37 | // include("database", "ply-database", "ply-tracker") -------------------------------------------------------------------------------- /updater.properties: -------------------------------------------------------------------------------- 1 | @fabric=https://maven.fabricmc.net/ 2 | @maven=https://repo.maven.apache.org/maven2/ 3 | @oss.sonatype=https://oss.sonatype.org/content/repositories/snapshots/ 4 | @default=@maven 5 | $minecraft.target=1.18.1 6 | $minecraft.snapshot=1.19 7 | $modding.api=fabric 8 | $properties=gradle.properties 9 | $recursive=true 10 | minecraft_required=$minecraft.required 11 | minecraft_version=$minecraft.target 12 | yarn_mappings=$mappings 13 | loader_version=$loader 14 | postgres_version=@maven,org.postgresql,postgresql 15 | fabric_loader_version=@fabric,net.fabricmc,fabric-loader 16 | fabric_api_version=@fabric,net.fabricmc.fabric-api,fabric-api 17 | fabric_permissions_version=@oss.sonatype,me.lucko,fabric-permissions-api 18 | # systemProp.loom_version=@fabric,fabric-loom,fabric-loom.gradle.plugin --------------------------------------------------------------------------------