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 extends LivingEntity> 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 |
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 |
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 |
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 |
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 extends PlayerEntity> players, byte permissions);
18 |
19 | void modifyPlayers(Collection extends PlayerEntity> players, short permissions);
20 |
21 | void removePlayers(Collection extends PlayerEntity> 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 extends MobEntity> 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 extends LivingEntity> 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 |
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 extends LivingEntity> 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
--------------------------------------------------------------------------------