summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/keuin/blame/Blame.java43
-rw-r--r--src/main/java/com/keuin/blame/EventHandler.java116
-rw-r--r--src/main/java/com/keuin/blame/SubmitWorker.java88
-rw-r--r--src/main/java/com/keuin/blame/adapter/AttackEntityAdapter.java26
-rw-r--r--src/main/java/com/keuin/blame/adapter/BreakBlockAdapter.java23
-rw-r--r--src/main/java/com/keuin/blame/adapter/UseBlockAdapter.java24
-rw-r--r--src/main/java/com/keuin/blame/adapter/UseEntityAdapter.java26
-rw-r--r--src/main/java/com/keuin/blame/adapter/UseItemAdapter.java24
-rw-r--r--src/main/java/com/keuin/blame/adapter/handler/AttackEntityHandler.java12
-rw-r--r--src/main/java/com/keuin/blame/adapter/handler/BreakBlockHandler.java11
-rw-r--r--src/main/java/com/keuin/blame/adapter/handler/UseBlockHandler.java10
-rw-r--r--src/main/java/com/keuin/blame/adapter/handler/UseEntityHandler.java12
-rw-r--r--src/main/java/com/keuin/blame/adapter/handler/UseItemHandler.java9
-rw-r--r--src/main/java/com/keuin/blame/config/BlameConfig.java40
-rw-r--r--src/main/java/com/keuin/blame/config/MongoConfig.java72
-rw-r--r--src/main/java/com/keuin/blame/data/LogEntry.java130
-rw-r--r--src/main/java/com/keuin/blame/data/LogEntryFactory.java46
-rw-r--r--src/main/java/com/keuin/blame/data/WorldPos.java56
-rw-r--r--src/main/java/com/keuin/blame/data/enums/ActionType.java37
-rw-r--r--src/main/java/com/keuin/blame/data/enums/IntegerEnum.java5
-rw-r--r--src/main/java/com/keuin/blame/data/enums/ObjectType.java32
-rw-r--r--src/main/java/com/keuin/blame/data/enums/codec/AbstractIntegerEnumCodec.java13
-rw-r--r--src/main/java/com/keuin/blame/data/enums/codec/ActionTypeCodec.java17
-rw-r--r--src/main/java/com/keuin/blame/data/enums/codec/ObjectTypeCodec.java17
-rw-r--r--src/main/java/com/keuin/blame/data/enums/transformer/ActionTypeTransformer.java12
-rw-r--r--src/main/java/com/keuin/blame/data/enums/transformer/ObjectTypeTransformer.java12
-rw-r--r--src/main/java/com/keuin/blame/util/MinecraftUtil.java11
-rw-r--r--src/main/java/com/keuin/blame/util/PrintUtil.java140
-rw-r--r--src/main/java/com/keuin/blame/util/UuidUtils.java20
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