diff options
Diffstat (limited to 'src/main/java/com/keuin')
29 files changed, 1083 insertions, 1 deletions
diff --git a/src/main/java/com/keuin/blame/Blame.java b/src/main/java/com/keuin/blame/Blame.java index 051a6b2..1e53a64 100644 --- a/src/main/java/com/keuin/blame/Blame.java +++ b/src/main/java/com/keuin/blame/Blame.java @@ -1,14 +1,55 @@ package com.keuin.blame; +import com.google.gson.Gson; +import com.keuin.blame.adapter.*; +import com.keuin.blame.config.BlameConfig; +import com.keuin.blame.util.PrintUtil; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.player.*; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.logging.Logger; public class Blame implements ModInitializer { + + private final Logger logger = Logger.getLogger(Blame.class.getName()); + + public static BlameConfig config; + @Override public void onInitialize() { // This code runs as soon as Minecraft is in a mod-load-ready state. // However, some things (like resources) may still be uninitialized. // Proceed with mild caution. - System.out.println("Hello Fabric world!"); + String configFileName = "blame.json"; + try { + // load config + File configFile = new File(configFileName); + if (!configFile.exists()) { + logger.severe(String.format("Failed to read configuration file %s. Blame will be disabled.", configFileName)); + return; + } + + Reader reader = Files.newBufferedReader(configFile.toPath(), StandardCharsets.UTF_8); + config = (new Gson()).fromJson(reader, BlameConfig.class); + } catch (IOException exception) { + logger.severe(String.format("Failed to read configuration file %s: %s. " + + "Blame will be disabled.", configFileName, exception)); + return; + } + + AttackEntityCallback.EVENT.register(new AttackEntityAdapter(EventHandler.INSTANCE)); + PlayerBlockBreakEvents.AFTER.register(new BreakBlockAdapter(EventHandler.INSTANCE)); + UseBlockCallback.EVENT.register(new UseBlockAdapter(EventHandler.INSTANCE)); + UseEntityCallback.EVENT.register(new UseEntityAdapter(EventHandler.INSTANCE)); + UseItemCallback.EVENT.register(new UseItemAdapter(EventHandler.INSTANCE)); + + ServerLifecycleEvents.SERVER_STARTED.register(PrintUtil.INSTANCE); } } diff --git a/src/main/java/com/keuin/blame/EventHandler.java b/src/main/java/com/keuin/blame/EventHandler.java new file mode 100644 index 0000000..a939759 --- /dev/null +++ b/src/main/java/com/keuin/blame/EventHandler.java @@ -0,0 +1,116 @@ +package com.keuin.blame; + +import com.keuin.blame.adapter.handler.*; +import com.keuin.blame.data.LogEntry; +import com.keuin.blame.data.LogEntryFactory; +import com.keuin.blame.data.enums.ActionType; +import com.keuin.blame.util.MinecraftUtil; +import com.keuin.blame.util.PrintUtil; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class EventHandler implements AttackEntityHandler, BreakBlockHandler, UseBlockHandler, UseEntityHandler, UseItemHandler { + + public static final EventHandler INSTANCE = new EventHandler(); + + private EventHandler() { + } + + @Override + public void onPlayerUseBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { + String worldString = MinecraftUtil.worldToString(world); + String blockId = Registry.BLOCK.getId(world.getBlockState(blockHitResult.getBlockPos()).getBlock()).toString(); + LogEntry entry = LogEntryFactory.playerWithBlock( + playerEntity.getUuid(), + playerEntity.getPos(), + worldString, + blockId, + blockHitResult.getBlockPos(), + worldString, + ActionType.BLOCK_USE + ); + SubmitWorker.INSTANCE.submit(entry); + PrintUtil.broadcast("use_block; block_id=" + blockId + "; world=" + worldString); + // TODO: 增加判断,事件触发的时候用户不一定真正使用了方块(也可能是无效的动作)。放置方块的时候也会触发这个事件 +// PrintUtil.broadcast(String.format("player %s use block %s", playerEntity.getName().getString(), world.getBlockState(blockHitResult.getBlockPos()))); + } + + @Override + public void onPlayerBreakBlock(World world, PlayerEntity playerEntity, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) { + String worldString = MinecraftUtil.worldToString(world); + String blockId = Registry.BLOCK.getId(blockState.getBlock()).toString(); + LogEntry entry = LogEntryFactory.playerWithBlock( + playerEntity.getUuid(), + playerEntity.getPos(), + worldString, + blockId, + blockPos, + worldString, + ActionType.BLOCK_BREAK + ); + SubmitWorker.INSTANCE.submit(entry); + PrintUtil.broadcast("break_block; block_id=" + blockId + "; world=" + worldString); +// PrintUtil.broadcast(String.format("player %s break block %s", playerEntity.getName().getString(), blockState)); + } + + @Override + public void onPlayerAttackEntity(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult) { + String entityId = Registry.ENTITY_TYPE.getId(entity.getType()).toString(); + String worldString = MinecraftUtil.worldToString(world); + LogEntry entry = LogEntryFactory.playerWithEntity( + playerEntity.getUuid(), + playerEntity.getPos(), + worldString, + entityId, + entity.getPos(), + worldString, + ActionType.ENTITY_ATTACK + ); + SubmitWorker.INSTANCE.submit(entry); + PrintUtil.broadcast("attack_entity; entity_id=" + entityId); +// PrintUtil.broadcast(String.format("player %s attack entity %s", playerEntity.getName().getString(), entity)); + } + + @Override + public void onPlayerUseEntity(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult) { + String entityId = Registry.ENTITY_TYPE.getId(entity.getType()).toString(); + String worldString = MinecraftUtil.worldToString(world); + LogEntry entry = LogEntryFactory.playerWithEntity( + playerEntity.getUuid(), + playerEntity.getPos(), + worldString, + entityId, + entity.getPos(), + worldString, + ActionType.ENTITY_USE + ); + SubmitWorker.INSTANCE.submit(entry); + PrintUtil.broadcast("use_entity; entity_id=" + entityId); + // TODO: 增加判断,无效的时候也会触发这个事件 + PrintUtil.broadcast(String.format("player %s use entity %s", playerEntity.getName().getString(), entity)); + } + + @Override + public void onPlayerUseItem(PlayerEntity playerEntity, World world, Hand hand) { + String itemId = Registry.ITEM.getId(playerEntity.getStackInHand(hand).getItem()).toString(); + LogEntry entry = LogEntryFactory.playerWithItem( + playerEntity.getUuid(), + playerEntity.getPos(), + MinecraftUtil.worldToString(world), + itemId, + ActionType.ITEM_USE + ); + SubmitWorker.INSTANCE.submit(entry); + PrintUtil.broadcast("use_item; item_id=" + itemId); +// PrintUtil.broadcast(String.format("player %s use item %s", playerEntity.getName().getString(), playerEntity.getStackInHand(hand))); + } +} diff --git a/src/main/java/com/keuin/blame/SubmitWorker.java b/src/main/java/com/keuin/blame/SubmitWorker.java new file mode 100644 index 0000000..1c47aaf --- /dev/null +++ b/src/main/java/com/keuin/blame/SubmitWorker.java @@ -0,0 +1,88 @@ +package com.keuin.blame; + +import com.keuin.blame.config.MongoConfig; +import com.keuin.blame.data.LogEntry; +import com.keuin.blame.data.enums.ActionType; +import com.keuin.blame.data.enums.ObjectType; +import com.keuin.blame.data.enums.codec.ActionTypeCodec; +import com.keuin.blame.data.enums.codec.ObjectTypeCodec; +import com.keuin.blame.data.enums.transformer.ActionTypeTransformer; +import com.keuin.blame.data.enums.transformer.ObjectTypeTransformer; +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.BSON; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.logging.Logger; + +public class SubmitWorker { + + public static final SubmitWorker INSTANCE = new SubmitWorker(Blame.config.getMongoConfig()); + private final Logger logger = Logger.getLogger(SubmitWorker.class.getName()); + + private final BlockingQueue<LogEntry> queue = new LinkedBlockingDeque<>(4096); + private final Thread thread = new Thread(SubmitWorker.this::run); + private boolean run = true; + + private final MongoConfig mongoConfig; + private final MongoClientSettings settings; + + + private SubmitWorker(MongoConfig mongoConfig) { + if (mongoConfig == null) + throw new IllegalArgumentException("mongo config cannot be null"); + this.mongoConfig = mongoConfig; + logger.fine("Connecting to MongoDB server `" + mongoConfig.getAddress() + + "` with database `" + mongoConfig.getDatabaseName() + + "` and collection `" + mongoConfig.getLogCollectionName() + "`."); + + BSON.addEncodingHook(ActionType.class, new ActionTypeTransformer()); + BSON.addEncodingHook(ObjectType.class, new ObjectTypeTransformer()); + + CodecRegistry codecRegistry = CodecRegistries.fromRegistries( + com.mongodb.MongoClient.getDefaultCodecRegistry(), + CodecRegistries.fromCodecs(new ActionTypeCodec(), new ObjectTypeCodec()) + ); + + settings = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(mongoConfig.getAddress())) + .codecRegistry(codecRegistry) + .build(); + thread.start(); + } + + public void submit(LogEntry entry) { + if (entry == null) + throw new IllegalArgumentException("entry cannot be null"); + queue.offer(entry); + } + + public void stop() { + thread.interrupt(); + this.run = false; + } + + private void run() { + try (final MongoClient mongoClient = MongoClients.create(settings)) { + final MongoDatabase db = mongoClient.getDatabase( + mongoConfig.getDatabaseName() + ); + final MongoCollection<LogEntry> collection = db.getCollection( + mongoConfig.getLogCollectionName(), LogEntry.class + ); + while (this.run) { + LogEntry entry = queue.take(); + collection.insertOne(entry); + } + } catch (InterruptedException ignored) { + } + } + +} diff --git a/src/main/java/com/keuin/blame/adapter/AttackEntityAdapter.java b/src/main/java/com/keuin/blame/adapter/AttackEntityAdapter.java new file mode 100644 index 0000000..f9016ae --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/AttackEntityAdapter.java @@ -0,0 +1,26 @@ +package com.keuin.blame.adapter; + +import com.keuin.blame.adapter.handler.AttackEntityHandler; +import net.fabricmc.fabric.api.event.player.AttackEntityCallback; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class AttackEntityAdapter implements AttackEntityCallback { + + private final AttackEntityHandler handler; + + public AttackEntityAdapter(AttackEntityHandler handler) { + this.handler = handler; + } + + @Override + public ActionResult interact(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult) { + handler.onPlayerAttackEntity(playerEntity, world, hand, entity, entityHitResult); + return ActionResult.PASS; + } +} diff --git a/src/main/java/com/keuin/blame/adapter/BreakBlockAdapter.java b/src/main/java/com/keuin/blame/adapter/BreakBlockAdapter.java new file mode 100644 index 0000000..58f402f --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/BreakBlockAdapter.java @@ -0,0 +1,23 @@ +package com.keuin.blame.adapter; + +import com.keuin.blame.adapter.handler.BreakBlockHandler; +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BreakBlockAdapter implements PlayerBlockBreakEvents.After { + + private final BreakBlockHandler handler; + + public BreakBlockAdapter(BreakBlockHandler handler) { + this.handler = handler; + } + + @Override + public void afterBlockBreak(World world, PlayerEntity playerEntity, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) { + handler.onPlayerBreakBlock(world, playerEntity, blockPos, blockState, blockEntity); + } +} diff --git a/src/main/java/com/keuin/blame/adapter/UseBlockAdapter.java b/src/main/java/com/keuin/blame/adapter/UseBlockAdapter.java new file mode 100644 index 0000000..2ff10e2 --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/UseBlockAdapter.java @@ -0,0 +1,24 @@ +package com.keuin.blame.adapter; + +import com.keuin.blame.adapter.handler.UseBlockHandler; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; + +public class UseBlockAdapter implements UseBlockCallback { + + private final UseBlockHandler handler; + + public UseBlockAdapter(UseBlockHandler handler) { + this.handler = handler; + } + + @Override + public ActionResult interact(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { + handler.onPlayerUseBlock(playerEntity, world, hand, blockHitResult); + return ActionResult.PASS; + } +} diff --git a/src/main/java/com/keuin/blame/adapter/UseEntityAdapter.java b/src/main/java/com/keuin/blame/adapter/UseEntityAdapter.java new file mode 100644 index 0000000..867a292 --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/UseEntityAdapter.java @@ -0,0 +1,26 @@ +package com.keuin.blame.adapter; + +import com.keuin.blame.adapter.handler.UseEntityHandler; +import net.fabricmc.fabric.api.event.player.UseEntityCallback; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class UseEntityAdapter implements UseEntityCallback { + + private final UseEntityHandler handler; + + public UseEntityAdapter(UseEntityHandler handler) { + this.handler = handler; + } + + @Override + public ActionResult interact(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult) { + handler.onPlayerUseEntity(playerEntity, world, hand, entity, entityHitResult); + return ActionResult.PASS; + } +} diff --git a/src/main/java/com/keuin/blame/adapter/UseItemAdapter.java b/src/main/java/com/keuin/blame/adapter/UseItemAdapter.java new file mode 100644 index 0000000..66adf99 --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/UseItemAdapter.java @@ -0,0 +1,24 @@ +package com.keuin.blame.adapter; + +import com.keuin.blame.adapter.handler.UseItemHandler; +import net.fabricmc.fabric.api.event.player.UseItemCallback; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; + +public class UseItemAdapter implements UseItemCallback { + + private final UseItemHandler handler; + + public UseItemAdapter(UseItemHandler handler) { + this.handler = handler; + } + + @Override + public TypedActionResult<ItemStack> interact(PlayerEntity playerEntity, World world, Hand hand) { + handler.onPlayerUseItem(playerEntity, world, hand); + return TypedActionResult.pass(ItemStack.EMPTY); + } +} diff --git a/src/main/java/com/keuin/blame/adapter/handler/AttackEntityHandler.java b/src/main/java/com/keuin/blame/adapter/handler/AttackEntityHandler.java new file mode 100644 index 0000000..3e1f307 --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/handler/AttackEntityHandler.java @@ -0,0 +1,12 @@ +package com.keuin.blame.adapter.handler; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public interface AttackEntityHandler { + void onPlayerAttackEntity(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult); +} diff --git a/src/main/java/com/keuin/blame/adapter/handler/BreakBlockHandler.java b/src/main/java/com/keuin/blame/adapter/handler/BreakBlockHandler.java new file mode 100644 index 0000000..4bf4d6b --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/handler/BreakBlockHandler.java @@ -0,0 +1,11 @@ +package com.keuin.blame.adapter.handler; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public interface BreakBlockHandler { + void onPlayerBreakBlock(World world, PlayerEntity playerEntity, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity); +} diff --git a/src/main/java/com/keuin/blame/adapter/handler/UseBlockHandler.java b/src/main/java/com/keuin/blame/adapter/handler/UseBlockHandler.java new file mode 100644 index 0000000..163278a --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/handler/UseBlockHandler.java @@ -0,0 +1,10 @@ +package com.keuin.blame.adapter.handler; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; + +public interface UseBlockHandler { + void onPlayerUseBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult); +} diff --git a/src/main/java/com/keuin/blame/adapter/handler/UseEntityHandler.java b/src/main/java/com/keuin/blame/adapter/handler/UseEntityHandler.java new file mode 100644 index 0000000..d8a744f --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/handler/UseEntityHandler.java @@ -0,0 +1,12 @@ +package com.keuin.blame.adapter.handler; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public interface UseEntityHandler { + void onPlayerUseEntity(PlayerEntity playerEntity, World world, Hand hand, Entity entity, @Nullable EntityHitResult entityHitResult); +} diff --git a/src/main/java/com/keuin/blame/adapter/handler/UseItemHandler.java b/src/main/java/com/keuin/blame/adapter/handler/UseItemHandler.java new file mode 100644 index 0000000..ca4b965 --- /dev/null +++ b/src/main/java/com/keuin/blame/adapter/handler/UseItemHandler.java @@ -0,0 +1,9 @@ +package com.keuin.blame.adapter.handler; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.world.World; + +public interface UseItemHandler { + void onPlayerUseItem(PlayerEntity playerEntity, World world, Hand hand); +} diff --git a/src/main/java/com/keuin/blame/config/BlameConfig.java b/src/main/java/com/keuin/blame/config/BlameConfig.java new file mode 100644 index 0000000..ace8fa3 --- /dev/null +++ b/src/main/java/com/keuin/blame/config/BlameConfig.java @@ -0,0 +1,40 @@ +package com.keuin.blame.config; + +import com.google.gson.annotations.SerializedName; + +import java.util.Objects; + +public class BlameConfig { + + @SerializedName("database") + private final MongoConfig mongoConfig; + + + public BlameConfig(MongoConfig mongoConfig) { + this.mongoConfig = mongoConfig; + } + + public MongoConfig getMongoConfig() { + return mongoConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlameConfig that = (BlameConfig) o; + return Objects.equals(mongoConfig, that.mongoConfig); + } + + @Override + public int hashCode() { + return Objects.hash(mongoConfig); + } + + @Override + public String toString() { + return "BlameConfig{" + + "mongoConfig=" + mongoConfig + + '}'; + } +} diff --git a/src/main/java/com/keuin/blame/config/MongoConfig.java b/src/main/java/com/keuin/blame/config/MongoConfig.java new file mode 100644 index 0000000..628af51 --- /dev/null +++ b/src/main/java/com/keuin/blame/config/MongoConfig.java @@ -0,0 +1,72 @@ +package com.keuin.blame.config; + +import com.google.gson.annotations.SerializedName; + +import java.util.Objects; + +public class MongoConfig { + + private final String address; + private final String username; + private final String password; + @SerializedName("database") + private final String databaseName; + @SerializedName("collection") + private final String logCollectionName; + + public MongoConfig(String address, String username, String password, String databaseName, String logCollectionName) { + this.address = address; + this.username = username; + this.password = password; + this.databaseName = databaseName; + this.logCollectionName = logCollectionName; + } + + public String getAddress() { + return address; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getDatabaseName() { + return databaseName; + } + + public String getLogCollectionName() { + return logCollectionName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MongoConfig that = (MongoConfig) o; + return Objects.equals(address, that.address) && + Objects.equals(username, that.username) && + Objects.equals(password, that.password) && + Objects.equals(databaseName, that.databaseName) && + Objects.equals(logCollectionName, that.logCollectionName); + } + + @Override + public int hashCode() { + return Objects.hash(address, username, password, databaseName, logCollectionName); + } + + @Override + public String toString() { + return "MongoConfig{" + + "address='" + address + '\'' + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", databaseName='" + databaseName + '\'' + + ", logCollectionName='" + logCollectionName + '\'' + + '}'; + } +} diff --git a/src/main/java/com/keuin/blame/data/LogEntry.java b/src/main/java/com/keuin/blame/data/LogEntry.java new file mode 100644 index 0000000..30037c0 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/LogEntry.java @@ -0,0 +1,130 @@ +package com.keuin.blame.data; + +import com.keuin.blame.data.enums.ActionType; +import com.keuin.blame.data.enums.ObjectType; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.Objects; +import java.util.UUID; + +public class LogEntry { + + // { + // "version": 1, // int + // "subject": { + // "uuid": player_uuid_bytes, // bytes + // "id": player_id, // string + // "pos": { + // "world": world_id, // string + // "x": pos_x, // float + // "y": pos_y, // float + // "z": pos_z, // float + // } + // }, + // "action": BLOCK_BREAK | BLOCK_PLACE | BLOCK_USE | ENTITY_USE | ENTITY_ATTACK | ITEM_USE, // int + // "object": { + // "type": OBJECT_BLOCK | OBJECT_ENTITY, // int + // "id": object_id, // string + // "pos": { + // "world": world_id, // string + // "x": pos_x, // float + // "y": pos_y, // float + // "z": pos_z, // float + // } + // } + //} + + @BsonProperty("version") + private final int version = 1; + @BsonProperty("timestamp_millis") + private final long timeMillis; + @BsonProperty("subject_uuid") + private final String subjectUUID; // TODO: use Binary instead (BasicDBObject("_id", Binary(session.getIp().getAddress()))) (https://stackoverflow.com/questions/30566905/store-byte-in-mongodb-using-java/40843195) + @BsonProperty("subject_pos") + private final WorldPos subjectPos; // TODO: write codec and transformer for this + @BsonProperty("action_type") + private final ActionType actionType; + @BsonProperty("object_type") + private final ObjectType objectType; + @BsonProperty("object_id") + private final String objectId; + @BsonProperty("object_pos") + private final WorldPos objectPos; + + public LogEntry(long timeMillis, UUID subjectUUID, WorldPos subjectPos, ActionType actionType, ObjectType objectType, String objectId, WorldPos objectPos) { +// this.subjectUUID = UuidUtils.asBytes(subjectUUID); +// this.subjectUUID + if (subjectUUID == null) + throw new IllegalArgumentException("subjectUUID cannot be null"); + if (subjectPos == null) + throw new IllegalArgumentException("subjectPos cannot be null"); + if (actionType == null) + throw new IllegalArgumentException("actionType cannot be null"); + if (objectType == null) + throw new IllegalArgumentException("objectType cannot be null"); + if (objectId == null) + throw new IllegalArgumentException("objectId cannot be null"); + if (objectPos == null) + throw new IllegalArgumentException("objectPos cannot be null"); + + this.timeMillis = timeMillis; + this.subjectUUID = subjectUUID.toString(); + this.subjectPos = subjectPos; + this.actionType = actionType; + this.objectType = objectType; + this.objectId = objectId; + this.objectPos = objectPos; + } + + public int getVersion() { + return 1; + } + + public long getTimeMillis() { + return timeMillis; + } + + public UUID getSubjectUUID() { + return UUID.fromString(subjectUUID); + } + + public WorldPos getSubjectPos() { + return subjectPos; + } + + public ActionType getActionType() { + return actionType; + } + + public ObjectType getObjectType() { + return objectType; + } + + public String getObjectId() { + return objectId; + } + + public WorldPos getObjectPos() { + return objectPos; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LogEntry entry = (LogEntry) o; + return version == entry.version && + timeMillis == entry.timeMillis && + Objects.equals(subjectUUID, entry.subjectUUID) && + Objects.equals(subjectPos, entry.subjectPos) && + actionType == entry.actionType && + objectType == entry.objectType && + Objects.equals(objectId, entry.objectId) && + Objects.equals(objectPos, entry.objectPos); + } + + @Override + public int hashCode() { + return Objects.hash(version, timeMillis, subjectUUID, subjectPos, actionType, objectType, objectId, objectPos); + } +} diff --git a/src/main/java/com/keuin/blame/data/LogEntryFactory.java b/src/main/java/com/keuin/blame/data/LogEntryFactory.java new file mode 100644 index 0000000..1e2d5d1 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/LogEntryFactory.java @@ -0,0 +1,46 @@ +package com.keuin.blame.data; + +import com.keuin.blame.data.enums.ActionType; +import com.keuin.blame.data.enums.ObjectType; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; + +import java.util.UUID; + +public class LogEntryFactory { + public static LogEntry playerWithBlock(UUID playerUUID, Vec3d playerPos, String playerWorld, String blockId, Vec3i blockPos, String blockWorld, ActionType actionType) { + return new LogEntry( + System.currentTimeMillis(), + playerUUID, + new WorldPos(playerWorld, playerPos.x, playerPos.y, playerPos.z), + actionType, + ObjectType.BLOCK, + blockId, + new WorldPos(blockWorld, blockPos.getX(), blockPos.getY(), blockPos.getZ()) + ); + } + + public static LogEntry playerWithEntity(UUID playerUUID, Vec3d playerPos, String playerWorld, String entityId, Vec3d entityPos, String entityWorld, ActionType actionType) { + return new LogEntry( + System.currentTimeMillis(), + playerUUID, + new WorldPos(playerWorld, playerPos.x, playerPos.y, playerPos.z), + actionType, + ObjectType.ENTITY, + entityId, + new WorldPos(entityWorld, entityPos.x, entityPos.y, entityPos.z) + ); + } + + public static LogEntry playerWithItem(UUID playerUUID, Vec3d playerPos, String playerWorld, String itemId, ActionType actionType) { + return new LogEntry( + System.currentTimeMillis(), + playerUUID, + new WorldPos(playerWorld, playerPos.x, playerPos.y, playerPos.z), + actionType, + ObjectType.ENTITY, + itemId, + WorldPos.NULL_POS + ); + } +} diff --git a/src/main/java/com/keuin/blame/data/WorldPos.java b/src/main/java/com/keuin/blame/data/WorldPos.java new file mode 100644 index 0000000..aeda093 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/WorldPos.java @@ -0,0 +1,56 @@ +package com.keuin.blame.data; + +import java.util.Objects; + +public class WorldPos { + + // immutable + + private final String world; + private final double x; + private final double y; + private final double z; + + public static final WorldPos NULL_POS = new WorldPos("", 0, 0, 0); + + public WorldPos(String world, double x, double y, double z) { + if (world == null) + throw new IllegalArgumentException("world string must not be null"); + this.world = world; + this.x = x; + this.y = y; + this.z = z; + } + + public String getWorld() { + return world; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WorldPos worldPos = (WorldPos) o; + return Double.compare(worldPos.x, x) == 0 && + Double.compare(worldPos.y, y) == 0 && + Double.compare(worldPos.z, z) == 0 && + world.equals(worldPos.world); + } + + @Override + public int hashCode() { + return Objects.hash(world, x, y, z); + } +} diff --git a/src/main/java/com/keuin/blame/data/enums/ActionType.java b/src/main/java/com/keuin/blame/data/enums/ActionType.java new file mode 100644 index 0000000..dcf2aa7 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/ActionType.java @@ -0,0 +1,37 @@ +package com.keuin.blame.data.enums; + +public enum ActionType implements IntegerEnum { + + BLOCK_BREAK(1), + BLOCK_PLACE(2), + BLOCK_USE(3), + ENTITY_ATTACK(4), + ENTITY_USE(5), + ITEM_USE(6); + + private final int value; + + ActionType(int value) { + this.value = value; + } + + public static ActionType parseInt(int value) { + for (ActionType actionType : ActionType.values()) { + if (actionType.value == value) + return actionType; + } + return null; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return "ActionType{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/com/keuin/blame/data/enums/IntegerEnum.java b/src/main/java/com/keuin/blame/data/enums/IntegerEnum.java new file mode 100644 index 0000000..6e9ea50 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/IntegerEnum.java @@ -0,0 +1,5 @@ +package com.keuin.blame.data.enums; + +public interface IntegerEnum { + int getValue(); +} diff --git a/src/main/java/com/keuin/blame/data/enums/ObjectType.java b/src/main/java/com/keuin/blame/data/enums/ObjectType.java new file mode 100644 index 0000000..b3b1d67 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/ObjectType.java @@ -0,0 +1,32 @@ +package com.keuin.blame.data.enums; + +public enum ObjectType implements IntegerEnum { + + BLOCK(1), ENTITY(2); + + private final int value; + + ObjectType(int value) { + this.value = value; + } + + public static ObjectType parseInt(int value) { + for (ObjectType objectType : ObjectType.values()) { + if (objectType.value == value) + return objectType; + } + return null; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return "ObjectType{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/com/keuin/blame/data/enums/codec/AbstractIntegerEnumCodec.java b/src/main/java/com/keuin/blame/data/enums/codec/AbstractIntegerEnumCodec.java new file mode 100644 index 0000000..98f871a --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/codec/AbstractIntegerEnumCodec.java @@ -0,0 +1,13 @@ +package com.keuin.blame.data.enums.codec; + +import com.keuin.blame.data.enums.IntegerEnum; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.EncoderContext; + +public abstract class AbstractIntegerEnumCodec<T extends IntegerEnum> implements Codec<T> { + @Override + public void encode(BsonWriter writer, T value, EncoderContext encoderContext) { + writer.writeInt32(value.getValue()); + } +} diff --git a/src/main/java/com/keuin/blame/data/enums/codec/ActionTypeCodec.java b/src/main/java/com/keuin/blame/data/enums/codec/ActionTypeCodec.java new file mode 100644 index 0000000..a7fc228 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/codec/ActionTypeCodec.java @@ -0,0 +1,17 @@ +package com.keuin.blame.data.enums.codec; + +import com.keuin.blame.data.enums.ActionType; +import org.bson.BsonReader; +import org.bson.codecs.DecoderContext; + +public class ActionTypeCodec extends AbstractIntegerEnumCodec<ActionType> { + @Override + public ActionType decode(BsonReader reader, DecoderContext decoderContext) { + return ActionType.parseInt(reader.readInt32()); + } + + @Override + public Class<ActionType> getEncoderClass() { + return ActionType.class; + } +} diff --git a/src/main/java/com/keuin/blame/data/enums/codec/ObjectTypeCodec.java b/src/main/java/com/keuin/blame/data/enums/codec/ObjectTypeCodec.java new file mode 100644 index 0000000..8b47cc6 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/codec/ObjectTypeCodec.java @@ -0,0 +1,17 @@ +package com.keuin.blame.data.enums.codec; + +import com.keuin.blame.data.enums.ObjectType; +import org.bson.BsonReader; +import org.bson.codecs.DecoderContext; + +public class ObjectTypeCodec extends AbstractIntegerEnumCodec<ObjectType> { + @Override + public ObjectType decode(BsonReader reader, DecoderContext decoderContext) { + return ObjectType.parseInt(reader.readInt32()); + } + + @Override + public Class<ObjectType> getEncoderClass() { + return ObjectType.class; + } +} diff --git a/src/main/java/com/keuin/blame/data/enums/transformer/ActionTypeTransformer.java b/src/main/java/com/keuin/blame/data/enums/transformer/ActionTypeTransformer.java new file mode 100644 index 0000000..b50118d --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/transformer/ActionTypeTransformer.java @@ -0,0 +1,12 @@ +package com.keuin.blame.data.enums.transformer; + +import com.keuin.blame.data.enums.ActionType; +import org.bson.Transformer; + +public class ActionTypeTransformer implements Transformer { + @Override + public Object transform(Object objectToTransform) { + ActionType actionType = (ActionType) objectToTransform; + return actionType.getValue(); + } +} diff --git a/src/main/java/com/keuin/blame/data/enums/transformer/ObjectTypeTransformer.java b/src/main/java/com/keuin/blame/data/enums/transformer/ObjectTypeTransformer.java new file mode 100644 index 0000000..e28b1b7 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/enums/transformer/ObjectTypeTransformer.java @@ -0,0 +1,12 @@ +package com.keuin.blame.data.enums.transformer; + +import com.keuin.blame.data.enums.ObjectType; +import org.bson.Transformer; + +public class ObjectTypeTransformer implements Transformer { + @Override + public Object transform(Object objectToTransform) { + ObjectType objectType = (ObjectType) objectToTransform; + return objectType.getValue(); + } +} diff --git a/src/main/java/com/keuin/blame/util/MinecraftUtil.java b/src/main/java/com/keuin/blame/util/MinecraftUtil.java new file mode 100644 index 0000000..ce8707e --- /dev/null +++ b/src/main/java/com/keuin/blame/util/MinecraftUtil.java @@ -0,0 +1,11 @@ +package com.keuin.blame.util; + +import net.minecraft.world.World; + +public class MinecraftUtil { + + public static String worldToString(World world) { + return world.getRegistryKey().getValue().toString(); + } + +} diff --git a/src/main/java/com/keuin/blame/util/PrintUtil.java b/src/main/java/com/keuin/blame/util/PrintUtil.java new file mode 100644 index 0000000..fdd1ea0 --- /dev/null +++ b/src/main/java/com/keuin/blame/util/PrintUtil.java @@ -0,0 +1,140 @@ +package com.keuin.blame.util; + +import com.mojang.brigadier.context.CommandContext; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.network.MessageType; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Style; +import net.minecraft.util.Formatting; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.UUID; + + +public final class PrintUtil implements ServerLifecycleEvents.ServerStarted { + + private static final Object syncMessage = new Object(); + private static final Object syncBroadcast = new Object(); + + private static final Style broadcastStyle = Style.EMPTY.withColor(Formatting.AQUA); + private static final Style infoStyle = Style.EMPTY.withColor(Formatting.WHITE); + private static final Style stressStyle = Style.EMPTY.withColor(Formatting.AQUA); + private static final Style warnStyle = Style.EMPTY.withColor(Formatting.YELLOW); + private static final Style errorStyle = Style.EMPTY.withColor(Formatting.DARK_RED); + + private static final Logger LOGGER = LogManager.getLogger(); + private static final String LOG_HEADING = "[Blame]"; + private static PlayerManager playerManager = null; + + private static final UUID UUID_NULL = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + // Used to handle server started event, to get player manager + // You should put `ServerLifecycleEvents.SERVER_STARTED.register(PrintUtil.INSTANCE);` in the plugin init method + public static final PrintUtil INSTANCE = new PrintUtil(); + + private PrintUtil() { + } + + @Override + public void onServerStarted(MinecraftServer minecraftServer) { + PrintUtil.playerManager = minecraftServer.getPlayerManager(); + } + + public static void setPlayerManager(PlayerManager playerManager) { + if (PrintUtil.playerManager == null) + PrintUtil.playerManager = playerManager; + } + + public static void broadcast(String message) { + synchronized (syncBroadcast) { + if (playerManager != null) + playerManager.broadcastChatMessage( + new LiteralText(message).setStyle(broadcastStyle), + MessageType.SYSTEM, + UUID_NULL + ); + } + } + + public static CommandContext<ServerCommandSource> msgStress(CommandContext<ServerCommandSource> context, String messageText) { + return msgStress(context, messageText, false); + } + + public static CommandContext<ServerCommandSource> msgInfo(CommandContext<ServerCommandSource> context, String messageText) { + return msgInfo(context, messageText, false); + } + + public static CommandContext<ServerCommandSource> msgWarn(CommandContext<ServerCommandSource> context, String messageText) { + return msgWarn(context, messageText, false); + } + + public static CommandContext<ServerCommandSource> msgErr(CommandContext<ServerCommandSource> context, String messageText) { + return msgErr(context, messageText, false); + } + + public static CommandContext<ServerCommandSource> msgStress(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) { + return message(context, messageText, broadcastToOps, stressStyle); + } + + public static CommandContext<ServerCommandSource> msgInfo(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) { + return message(context, messageText, broadcastToOps, infoStyle); + } + + public static CommandContext<ServerCommandSource> msgWarn(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) { + return message(context, messageText, broadcastToOps, warnStyle); + } + + public static CommandContext<ServerCommandSource> msgErr(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) { + return message(context, messageText, broadcastToOps, errorStyle); + } + + private static CommandContext<ServerCommandSource> message(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps, Style style) { + synchronized (syncMessage) { + LiteralText text = new LiteralText(messageText); + text.setStyle(style); + context.getSource().sendFeedback(text, broadcastToOps); + } + return context; + } + + /** + * Print debug message on the server console. + * + * @param string the message. + */ + public static void debug(String string) { + LOGGER.debug(LOG_HEADING + " " + string); + } + + /** + * Print informative message on the server console. + * + * @param string the message. + */ + public static void info(String string) { + LOGGER.info(LOG_HEADING + " " + string); + } + + /** + * Print warning message on the server console. + * + * @param string the message. + */ + public static void warn(String string) { + LOGGER.warn(LOG_HEADING + " " + string); + } + + /** + * Print error message on the server console. + * + * @param string the message. + */ + public static void error(String string) { + LOGGER.error(LOG_HEADING + " " + string); + } + +} diff --git a/src/main/java/com/keuin/blame/util/UuidUtils.java b/src/main/java/com/keuin/blame/util/UuidUtils.java new file mode 100644 index 0000000..b282129 --- /dev/null +++ b/src/main/java/com/keuin/blame/util/UuidUtils.java @@ -0,0 +1,20 @@ +package com.keuin.blame.util; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UuidUtils { + public static UUID asUuid(byte[] bytes) { + ByteBuffer bb = ByteBuffer.wrap(bytes); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } + + public static byte[] asBytes(UUID uuid) { + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb.array(); + } +}
\ No newline at end of file |