├── .github ├── FUNDING.yml └── workflows │ ├── test.yml │ └── publish.yml ├── neoforge ├── gradle.properties └── src │ └── main │ ├── resources │ ├── grieflogger-neoforge.mixins.json │ └── META-INF │ │ └── neoforge.mods.toml │ └── java │ └── com │ └── daqem │ └── grieflogger │ └── neoforge │ ├── GriefLoggerNeoForge.java │ ├── mixin │ └── MixinBucketItem.java │ └── GriefLoggerPermissionsImpl.java ├── common ├── src │ └── main │ │ ├── resources │ │ ├── architectury.common.json │ │ ├── grieflogger.accesswidener │ │ ├── grieflogger-common.mixins.json │ │ └── assets │ │ │ └── grieflogger │ │ │ └── lang │ │ │ ├── zh_tw.json │ │ │ ├── en_us.json │ │ │ └── nl_nl.json │ │ └── java │ │ └── com │ │ └── daqem │ │ └── grieflogger │ │ ├── player │ │ ├── GriefLoggerPlayer.java │ │ └── GriefLoggerServerPlayer.java │ │ ├── model │ │ ├── Operation.java │ │ ├── history │ │ │ ├── IHistory.java │ │ │ ├── ContainerHistory.java │ │ │ ├── History.java │ │ │ ├── SessionHistory.java │ │ │ ├── ItemHistory.java │ │ │ └── BlockHistory.java │ │ ├── User.java │ │ ├── BlockPosition.java │ │ ├── action │ │ │ ├── IAction.java │ │ │ ├── Actions.java │ │ │ ├── BlockAction.java │ │ │ ├── SessionAction.java │ │ │ └── ItemAction.java │ │ ├── TimeUnit.java │ │ ├── Time.java │ │ └── SimpleItemStack.java │ │ ├── database │ │ ├── cache │ │ │ ├── Caches.java │ │ │ ├── ICache.java │ │ │ └── UserCache.java │ │ ├── repository │ │ │ ├── Repository.java │ │ │ ├── IRepository.java │ │ │ ├── LevelRepository.java │ │ │ ├── EntityRepository.java │ │ │ ├── MaterialRepository.java │ │ │ ├── UsernameRepository.java │ │ │ ├── ChatRepository.java │ │ │ ├── CommandRepository.java │ │ │ └── UserRepository.java │ │ ├── queue │ │ │ ├── IQueue.java │ │ │ ├── SqlTask.java │ │ │ └── Queue.java │ │ ├── dialect │ │ │ ├── IDatabaseDialect.java │ │ │ ├── MySQLDialect.java │ │ │ └── SQLiteDialect.java │ │ └── service │ │ │ ├── LevelService.java │ │ │ ├── EntityService.java │ │ │ ├── MaterialService.java │ │ │ ├── UsernameService.java │ │ │ ├── Services.java │ │ │ ├── UserService.java │ │ │ ├── ChatService.java │ │ │ ├── CommandService.java │ │ │ ├── SessionService.java │ │ │ ├── ItemService.java │ │ │ ├── ContainerService.java │ │ │ └── BlockService.java │ │ ├── thread │ │ ├── OnComplete.java │ │ ├── GriefLoggerThreadFactory.java │ │ └── ThreadManager.java │ │ ├── block │ │ ├── container │ │ │ ├── IContainerTransactionManager.java │ │ │ ├── ContainerHandler.java │ │ │ ├── ContainerTransactionManager.java │ │ │ └── ContainersTransactionManager.java │ │ └── BlockHandler.java │ │ ├── command │ │ ├── ICommand.java │ │ ├── filter │ │ │ ├── ExcludeFilter.java │ │ │ ├── IncludeFilter.java │ │ │ ├── Filters.java │ │ │ ├── IFilter.java │ │ │ ├── ActionFilter.java │ │ │ ├── UserFilter.java │ │ │ ├── RadiusFilter.java │ │ │ ├── ItemFilter.java │ │ │ └── TimeFilter.java │ │ ├── GriefLoggerCommand.java │ │ ├── InspectCommand.java │ │ ├── PageCommand.java │ │ ├── argument │ │ │ └── FilterArgument.java │ │ ├── page │ │ │ └── Page.java │ │ └── LookupCommand.java │ │ ├── event │ │ ├── AbstractEvent.java │ │ ├── ServerStartedEvent.java │ │ ├── item │ │ │ ├── ItemEvents.java │ │ │ ├── BreakItemEvent.java │ │ │ ├── ConsumeItemEvent.java │ │ │ ├── ShootItemEvent.java │ │ │ ├── ThrowItemEvent.java │ │ │ ├── SmeltItemEvent.java │ │ │ ├── DropItemEvent.java │ │ │ ├── CraftItemEvent.java │ │ │ └── PickupItemEvent.java │ │ ├── RegisterCommandEvent.java │ │ ├── block │ │ │ ├── RemoveBlockInteractionsEvent.java │ │ │ ├── PlaceBlockEvent.java │ │ │ ├── InspectBlockEvent.java │ │ │ ├── RemoveDoorInteractionsEvent.java │ │ │ ├── BlockEvents.java │ │ │ ├── BreakContainerEvent.java │ │ │ ├── LogBlockEvent.java │ │ │ ├── InspectDoorEvent.java │ │ │ ├── LeftClickBlockEvent.java │ │ │ ├── BreakBlockEvent.java │ │ │ ├── InspectContainerEvent.java │ │ │ └── RightClickBlockEvent.java │ │ ├── PlayerQuitEvent.java │ │ ├── ChatEvent.java │ │ ├── LevelLoadEvent.java │ │ ├── CommandEvent.java │ │ ├── PlayerJoinEvent.java │ │ ├── EntityEvents.java │ │ └── TickEvents.java │ │ ├── GriefLoggerPermissions.java │ │ ├── mixin │ │ ├── ItemMixin.java │ │ ├── ProjectileMixin.java │ │ ├── MixinBucketItem.java │ │ ├── MixinArmorStand.java │ │ └── MixinItemStack.java │ │ └── config │ │ └── GriefLoggerConfig.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── relocate_natives ├── requirements.txt ├── prepare.sh ├── prepare.ps1 ├── fix_modified_binary.py └── download_codesign.py ├── fabric └── src │ └── main │ ├── resources │ ├── grieflogger-fabric.mixins.json │ └── fabric.mod.json │ └── java │ └── com │ └── daqem │ └── grieflogger │ └── fabric │ ├── GriefLoggerFabricServer.java │ ├── GriefLoggerPermissionsImpl.java │ └── mixin │ └── MixinBucketItem.java ├── .gitignore ├── settings.gradle ├── changelog.md ├── gradle.properties └── gradlew.bat /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: daqem -------------------------------------------------------------------------------- /neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=neoforge -------------------------------------------------------------------------------- /common/src/main/resources/architectury.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessWidener": "grieflogger.accesswidener" 3 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DAQEM/GriefLogger/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /relocate_natives/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DAQEM/GriefLogger/HEAD/relocate_natives/requirements.txt -------------------------------------------------------------------------------- /relocate_natives/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | python -m venv .venv 6 | . ./.venv/bin/activate 7 | pip install -r requirements.txt 8 | -------------------------------------------------------------------------------- /relocate_natives/prepare.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | python -m venv .venv 4 | .\.venv\Scripts\activate 5 | pip install -r requirements.txt 6 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/player/GriefLoggerPlayer.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.player; 2 | 3 | public interface GriefLoggerPlayer { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/Operation.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | public enum Operation { 4 | REMOVE, 5 | ADD, 6 | NEUTRAL 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/cache/Caches.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.cache; 2 | 3 | public interface Caches { 4 | 5 | UserCache USER = new UserCache(); 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/Repository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | public abstract class Repository implements IRepository { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/thread/OnComplete.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.thread; 2 | 3 | @FunctionalInterface 4 | public interface OnComplete { 5 | 6 | void onComplete(T type); 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/resources/grieflogger.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v2 named 2 | 3 | accessible method net/minecraft/world/entity/projectile/arrow/AbstractArrow getPickupItem ()Lnet/minecraft/world/item/ItemStack; -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/IRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | public interface IRepository { 4 | 5 | void createTable(); 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/cache/ICache.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.cache; 2 | 3 | import java.util.Map; 4 | 5 | public interface ICache { 6 | 7 | Map getAllUsernames(); 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/queue/IQueue.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.queue; 2 | 3 | public interface IQueue { 4 | 5 | void add(SqlTask task); 6 | void execute(); 7 | void hello(); 8 | boolean isEmpty(); 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/block/container/IContainerTransactionManager.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.block.container; 2 | 3 | import net.minecraft.server.level.ServerPlayer; 4 | 5 | public interface IContainerTransactionManager { 6 | void finalize(ServerPlayer serverPlayer); 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/queue/SqlTask.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.queue; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | 6 | @FunctionalInterface 7 | public interface SqlTask { 8 | void execute(Connection connection) throws SQLException; 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/ICommand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command; 2 | 3 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 4 | import net.minecraft.commands.CommandSourceStack; 5 | 6 | public interface ICommand { 7 | 8 | LiteralArgumentBuilder getCommand(); 9 | } 10 | -------------------------------------------------------------------------------- /fabric/src/main/resources/grieflogger-fabric.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "com.daqem.grieflogger.fabric.mixin", 4 | "compatibilityLevel": "JAVA_21", 5 | "minVersion": "0.8", 6 | "client": [ 7 | ], 8 | "mixins": [ 9 | "MixinBucketItem" 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } -------------------------------------------------------------------------------- /neoforge/src/main/resources/grieflogger-neoforge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "com.daqem.grieflogger.neoforge.mixin", 4 | "compatibilityLevel": "JAVA_21", 5 | "minVersion": "0.8", 6 | "client": [ 7 | ], 8 | "mixins": [ 9 | "MixinBucketItem" 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.ipr 3 | run/ 4 | *.iws 5 | out/ 6 | *.iml 7 | .gradle/ 8 | .gradle-cache/ 9 | output/ 10 | bin/ 11 | libs/ 12 | 13 | .classpath 14 | .project 15 | .idea/ 16 | classes/ 17 | .metadata 18 | .vscode 19 | .settings 20 | *.launch 21 | 22 | relocate_natives/.venv/ 23 | relocate_natives/__pycache__/ 24 | relocate_natives/apple-codesign/ 25 | relocate_natives/cache/ -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/dialect/IDatabaseDialect.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.dialect; 2 | 3 | public interface IDatabaseDialect { 4 | 5 | String getInsertIgnore(); 6 | 7 | String getOnConflictUpdate(String key, String update); 8 | 9 | String getOnConflictDoNothing(String key); 10 | 11 | String getDataType(String type); 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url "https://maven.fabricmc.net/" } 4 | maven { url "https://maven.architectury.dev/" } 5 | maven { url "https://maven.minecraftforge.net/" } 6 | gradlePluginPortal() 7 | } 8 | } 9 | 10 | include 'common' 11 | include 'fabric' 12 | include 'neoforge' 13 | 14 | rootProject.name = "GriefLogger 1.21.11" 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/AbstractEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import dev.architectury.event.EventResult; 4 | 5 | public abstract class AbstractEvent { 6 | 7 | public static EventResult interrupt() { 8 | return EventResult.interruptFalse(); 9 | } 10 | 11 | public static EventResult pass() { 12 | return EventResult.pass(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/ServerStartedEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.i18n.LanguageManager; 4 | 5 | import dev.architectury.event.events.common.LifecycleEvent; 6 | 7 | public class ServerStartedEvent { 8 | 9 | public static void registerEvent() { 10 | LifecycleEvent.SERVER_STARTED.register(LanguageManager::load); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/daqem/grieflogger/fabric/GriefLoggerFabricServer.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.fabric; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.fabricmc.api.DedicatedServerModInitializer; 5 | 6 | public class GriefLoggerFabricServer implements DedicatedServerModInitializer { 7 | 8 | @Override 9 | public void onInitializeServer() { 10 | GriefLogger.init(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /neoforge/src/main/java/com/daqem/grieflogger/neoforge/GriefLoggerNeoForge.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.neoforge; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.neoforged.api.distmarker.Dist; 5 | import net.neoforged.fml.common.Mod; 6 | 7 | @Mod(value = GriefLogger.MOD_ID, dist = Dist.DEDICATED_SERVER) 8 | public class GriefLoggerNeoForge { 9 | 10 | public GriefLoggerNeoForge() { 11 | GriefLogger.init(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/resources/grieflogger-common.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "com.daqem.grieflogger.mixin", 4 | "compatibilityLevel": "JAVA_21", 5 | "minVersion": "0.8", 6 | "client": [ 7 | ], 8 | "mixins": [ 9 | "ItemMixin", 10 | "MixinArmorStand", 11 | "MixinBucketItem", 12 | "MixinItemStack", 13 | "MixinServerPlayer", 14 | "ProjectileMixin" 15 | ], 16 | "injectors": { 17 | "defaultRequire": 1 18 | } 19 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/ItemEvents.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import dev.architectury.event.events.common.PlayerEvent; 4 | 5 | public class ItemEvents { 6 | 7 | public static void registerEvents() { 8 | PlayerEvent.CRAFT_ITEM.register(CraftItemEvent::onCraftItem); 9 | PlayerEvent.PICKUP_ITEM_POST.register(PickupItemEvent::onPickupItem); 10 | PlayerEvent.SMELT_ITEM.register(SmeltItemEvent::onSmeltItem); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/RegisterCommandEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.command.GriefLoggerCommand; 4 | import dev.architectury.event.events.common.CommandRegistrationEvent; 5 | 6 | public class RegisterCommandEvent { 7 | 8 | public static void registerEvent() { 9 | CommandRegistrationEvent.EVENT.register((dispatcher, registry, selection) -> 10 | GriefLoggerCommand.registerCommand(dispatcher)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## [View changes here](https://github.com/DAQEM/GriefLogger/releases) 2 | 3 | [![BisectHosting code DAQEM for 25% off!](https://www.bisecthosting.com/partners/custom-banners/bb6b0cc7-75a1-4002-9257-561d8df48142.webp)](https://bisecthosting.com/DAQEM?r=GriefLogger+Changelog) 4 | 5 | ### [I trust BisectHosting with my servers, and you should too! Get 25% off your first month of a gaming server for new customers using DAQEM. They have outstanding support that's always there when you need it.](https://bisecthosting.com/DAQEM?r=GriefLogger+Changelog) 6 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/RemoveBlockInteractionsEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.event.AbstractEvent; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.world.level.Level; 7 | 8 | public class RemoveBlockInteractionsEvent extends AbstractEvent { 9 | 10 | public static void removeBlockInteractions(Level level, BlockPos pos) { 11 | Services.BLOCK.removeInteractionsForPosition(level, pos); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/thread/GriefLoggerThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.thread; 2 | 3 | import io.netty.util.concurrent.DefaultThreadFactory; 4 | 5 | public class GriefLoggerThreadFactory extends DefaultThreadFactory { 6 | 7 | private static final String THREAD_NAME = "GriefLogger thread"; 8 | 9 | public GriefLoggerThreadFactory() { 10 | super(THREAD_NAME); 11 | } 12 | 13 | @Override 14 | public Thread newThread(Runnable r) { 15 | Thread thread = super.newThread(r); 16 | thread.setName(THREAD_NAME); 17 | return thread; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/PlayerQuitEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.action.SessionAction; 5 | import dev.architectury.event.events.common.PlayerEvent; 6 | 7 | public class PlayerQuitEvent { 8 | 9 | public static void registerEvent() { 10 | PlayerEvent.PLAYER_QUIT.register(player -> 11 | Services.SESSION.insert( 12 | player.getUUID(), 13 | player.level(), 14 | player.getOnPos(), 15 | SessionAction.QUIT)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/IHistory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import com.daqem.grieflogger.model.BlockPosition; 4 | import com.daqem.grieflogger.model.Time; 5 | import com.daqem.grieflogger.model.User; 6 | import com.daqem.grieflogger.model.action.IAction; 7 | import net.minecraft.network.chat.Component; 8 | 9 | public interface IHistory { 10 | 11 | Time getTime(); 12 | 13 | User getUser(); 14 | 15 | BlockPosition getPosition(); 16 | 17 | IAction getAction(); 18 | 19 | Component getComponent(); 20 | 21 | Component getMaterialComponent(); 22 | 23 | Component getComponentWithPos(); 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/BreakItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.model.SimpleItemStack; 4 | import com.daqem.grieflogger.model.action.ItemAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | 9 | public class BreakItemEvent { 10 | 11 | public static void breakItem(Player player, ItemStack itemStack) { 12 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 13 | serverPlayer.griefLogger$addItemToQueue(ItemAction.BREAK_ITEM, new SimpleItemStack(itemStack)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/LevelService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.LevelRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | 7 | public class LevelService { 8 | 9 | private final LevelRepository levelRepository; 10 | 11 | public LevelService(Database database) { 12 | this.levelRepository = new LevelRepository(database); 13 | } 14 | 15 | public void createTable() { 16 | levelRepository.createTable(); 17 | } 18 | 19 | public void insert(String name) { 20 | levelRepository.insert(name); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/ConsumeItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.model.SimpleItemStack; 4 | import com.daqem.grieflogger.model.action.ItemAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | 9 | public class ConsumeItemEvent { 10 | 11 | public static void consumeItem(Player player, ItemStack itemStack) { 12 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 13 | serverPlayer.griefLogger$addItemToQueue(ItemAction.CONSUME_ITEM, new SimpleItemStack(itemStack)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/EntityService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.EntityRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | 7 | public class EntityService { 8 | 9 | private final EntityRepository entityRepository; 10 | 11 | public EntityService(Database database) { 12 | this.entityRepository = new EntityRepository(database); 13 | } 14 | 15 | public void createTable() { 16 | entityRepository.createTable(); 17 | } 18 | 19 | public void insert(String name) { 20 | entityRepository.insert(name); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/ShootItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.model.SimpleItemStack; 4 | import com.daqem.grieflogger.model.action.ItemAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | 9 | public class ShootItemEvent { 10 | public static void shootItem(Player player, ItemStack itemStack) { 11 | if (player instanceof GriefLoggerServerPlayer serverPlayer && itemStack != null) { 12 | serverPlayer.griefLogger$addItemToQueue(ItemAction.SHOOT_ITEM, new SimpleItemStack(itemStack)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/ThrowItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.model.SimpleItemStack; 4 | import com.daqem.grieflogger.model.action.ItemAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.ItemStack; 8 | 9 | public class ThrowItemEvent { 10 | public static void throwItem(Player player, ItemStack itemStack) { 11 | if (player instanceof GriefLoggerServerPlayer serverPlayer && itemStack != null) { 12 | serverPlayer.griefLogger$addItemToQueue(ItemAction.THROW_ITEM, new SimpleItemStack(itemStack)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/MaterialService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.MaterialRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | 7 | public class MaterialService { 8 | 9 | private final MaterialRepository materialRepository; 10 | 11 | public MaterialService(Database database) { 12 | this.materialRepository = new MaterialRepository(database); 13 | } 14 | 15 | public void createTable() { 16 | materialRepository.createTable(); 17 | } 18 | 19 | public void insert(String material) { 20 | materialRepository.insert(material); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/ChatEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import dev.architectury.event.EventResult; 5 | 6 | public class ChatEvent { 7 | 8 | public static void registerEvent() { 9 | dev.architectury.event.events.common.ChatEvent.RECEIVED.register((player, component) -> { 10 | if (player != null) { 11 | Services.CHAT.insert( 12 | player.getUUID(), 13 | player.level(), 14 | player.getOnPos(), 15 | component.getString() 16 | ); 17 | } 18 | return EventResult.pass(); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/PlaceBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.model.action.BlockAction; 4 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.world.entity.Entity; 7 | import net.minecraft.world.level.Level; 8 | import net.minecraft.world.level.block.state.BlockState; 9 | 10 | public class PlaceBlockEvent { 11 | 12 | public static void placeBlock(Level level, BlockPos pos, BlockState state, Entity placer) { 13 | if (placer instanceof GriefLoggerServerPlayer serverPlayer) { 14 | LogBlockEvent.logBlock(serverPlayer, level, state, pos, BlockAction.PLACE_BLOCK); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/InspectBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.event.AbstractEvent; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import dev.architectury.event.EventResult; 7 | import net.minecraft.core.BlockPos; 8 | 9 | public class InspectBlockEvent extends AbstractEvent { 10 | 11 | public static EventResult inspectBlock(GriefLoggerServerPlayer player, BlockPos pos) { 12 | Services.BLOCK.getBlockHistoryAsync( 13 | player.grieflogger$asServerPlayer().level(), 14 | pos, 15 | player::grieflogger$sendInspectMessage); 16 | return interrupt(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/SmeltItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.event.AbstractEvent; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import net.minecraft.world.entity.player.Player; 8 | import net.minecraft.world.item.ItemStack; 9 | 10 | public class SmeltItemEvent extends AbstractEvent { 11 | 12 | public static void onSmeltItem(Player player, ItemStack itemStack) { 13 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 14 | serverPlayer.griefLogger$addItemToQueue(ItemAction.CRAFT_ITEM, new SimpleItemStack(itemStack)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/UsernameService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.UsernameRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | 7 | import java.util.UUID; 8 | 9 | public class UsernameService { 10 | 11 | private final UsernameRepository usernameRepository; 12 | 13 | public UsernameService(Database database) { 14 | this.usernameRepository = new UsernameRepository(database); 15 | } 16 | 17 | public void createTable() { 18 | usernameRepository.createTable(); 19 | } 20 | 21 | public void insert(UUID uuid, String name) { 22 | usernameRepository.insert(System.currentTimeMillis(), uuid.toString(), name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/User.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.minecraft.network.chat.Component; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | public class User { 11 | 12 | private final String name; 13 | private final @Nullable UUID uuid; 14 | 15 | public User(String name, @Nullable UUID uuid) { 16 | this.name = name; 17 | this.uuid = uuid; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public Optional getUuid() { 25 | return Optional.ofNullable(uuid); 26 | } 27 | 28 | public Component getNameComponent() { 29 | return GriefLogger.themedLiteral(name); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/LevelLoadEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import dev.architectury.event.events.common.LifecycleEvent; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class LevelLoadEvent { 10 | 11 | private static final List registeredLevels = new ArrayList<>(); 12 | 13 | public static void registerEvent() { 14 | LifecycleEvent.SERVER_LEVEL_LOAD.register(level -> { 15 | String levelName = level.dimension().identifier().toString(); 16 | if (!registeredLevels.contains(levelName)) { 17 | registeredLevels.add(levelName); 18 | Services.LEVEL.insert(level.dimension().identifier().toString()); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/DropItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.event.AbstractEvent; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import dev.architectury.event.EventResult; 8 | import net.minecraft.world.entity.item.ItemEntity; 9 | import net.minecraft.world.entity.player.Player; 10 | 11 | public class DropItemEvent extends AbstractEvent { 12 | 13 | public static void onDropItem(Player player, ItemEntity itemEntity) { 14 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 15 | serverPlayer.griefLogger$addItemToQueue(ItemAction.DROP_ITEM, new SimpleItemStack(itemEntity.getItem())); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/CraftItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.event.AbstractEvent; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import net.minecraft.world.Container; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.item.ItemStack; 10 | 11 | public class CraftItemEvent extends AbstractEvent { 12 | 13 | public static void onCraftItem(Player player, ItemStack itemStack, Container container) { 14 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 15 | serverPlayer.griefLogger$addItemToQueue(ItemAction.CRAFT_ITEM, new SimpleItemStack(itemStack)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/BlockPosition.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.network.chat.ClickEvent; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.network.chat.HoverEvent; 8 | import net.minecraft.network.chat.Style; 9 | 10 | public record BlockPosition(int x, int y, int z) { 11 | 12 | public Component getComponent() { 13 | return GriefLogger.translate("lookup.position", x, y, z) 14 | .withStyle(Style.EMPTY.withColor(ChatFormatting.GRAY) 15 | .withHoverEvent(new HoverEvent.ShowText(GriefLogger.literal("Click to teleport to this position."))) 16 | .withClickEvent(new ClickEvent.RunCommand("/tp " + x + " " + y + " " + z))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/item/PickupItemEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.item; 2 | 3 | import com.daqem.grieflogger.event.AbstractEvent; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import net.minecraft.world.entity.item.ItemEntity; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.item.ItemStack; 10 | 11 | public class PickupItemEvent extends AbstractEvent { 12 | 13 | public static void onPickupItem(Player player, ItemEntity itemEntity, ItemStack itemStack) { 14 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 15 | serverPlayer.griefLogger$addItemToQueue(ItemAction.PICKUP_ITEM, new SimpleItemStack(itemStack)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "${mod_id}", 4 | "version": "${version}", 5 | "name": "${mod_name}", 6 | "description": "${mod_description}", 7 | "authors": [ 8 | "${mod_author}" 9 | ], 10 | "contact": { 11 | "homepage": "${mod_website}", 12 | "sources": "${mod_repository}" 13 | }, 14 | "license": "${mod_license}", 15 | "icon": "assets/grieflogger/icon.png", 16 | "environment": "*", 17 | "entrypoints": { 18 | "server": [ 19 | "com.daqem.grieflogger.fabric.GriefLoggerFabricServer" 20 | ] 21 | }, 22 | "mixins": [ 23 | "grieflogger-common.mixins.json", 24 | "grieflogger-fabric.mixins.json" 25 | ], 26 | "depends": { 27 | "fabric": "*", 28 | "minecraft": ">=${minecraft_version}", 29 | "architectury": ">=${architectury_version}", 30 | "yamlconfig": ">=${config_library_version}" 31 | } 32 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/cache/UserCache.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.cache; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.database.service.UserService; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class UserCache implements ICache { 11 | 12 | private final static UserService userService = Services.USER; 13 | 14 | private long usernameTime = 0; 15 | private final Map usernames = new HashMap<>(); 16 | 17 | public Map getAllUsernames() { 18 | if (usernameTime + 1000 < System.currentTimeMillis()) { 19 | usernames.clear(); 20 | usernames.putAll(userService.getAllUsernames()); 21 | usernameTime = System.currentTimeMillis(); 22 | } 23 | return usernames; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/GriefLoggerPermissions.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger; 2 | 3 | import dev.architectury.injectables.annotations.ExpectPlatform; 4 | import net.minecraft.commands.CommandSourceStack; 5 | 6 | public class GriefLoggerPermissions { 7 | 8 | /** 9 | * Checks permissions using the platform's specific API (LuckPerms/PermissionsAPI) 10 | * if available. Otherwise, falls back to the standard OP level check. 11 | * 12 | * @param source The command source. 13 | * @param permissionNode The string permission node (e.g., "grieflogger.command.inspect"). 14 | * @param fallbackLevel The OP level to require if the permission API is missing (usually 2). 15 | * @return True if the source has permission. 16 | */ 17 | @ExpectPlatform 18 | public static boolean check(CommandSourceStack source, String permissionNode, int fallbackLevel) { 19 | throw new AssertionError(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/dialect/MySQLDialect.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.dialect; 2 | 3 | public class MySQLDialect implements IDatabaseDialect { 4 | 5 | @Override 6 | public String getInsertIgnore() { 7 | return "INSERT IGNORE"; 8 | } 9 | 10 | @Override 11 | public String getOnConflictUpdate(String key, String update) { 12 | return "ON DUPLICATE KEY UPDATE " + update; 13 | } 14 | 15 | @Override 16 | public String getOnConflictDoNothing(String key) { 17 | return "ON DUPLICATE KEY UPDATE " + key + " = " + key; 18 | } 19 | 20 | @Override 21 | public String getDataType(String type) { 22 | return switch (type) { 23 | case "integer" -> "int"; 24 | case "bigint" -> "bigint"; 25 | case "text" -> "text"; 26 | case "varchar" -> "varchar"; 27 | default -> type; 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/dialect/SQLiteDialect.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.dialect; 2 | 3 | public class SQLiteDialect implements IDatabaseDialect { 4 | 5 | @Override 6 | public String getInsertIgnore() { 7 | return "INSERT OR IGNORE"; 8 | } 9 | 10 | @Override 11 | public String getOnConflictUpdate(String key, String update) { 12 | return "ON CONFLICT(" + key + ") DO UPDATE SET " + update; 13 | } 14 | 15 | @Override 16 | public String getOnConflictDoNothing(String key) { 17 | return "ON CONFLICT(" + key + ") DO NOTHING"; 18 | } 19 | 20 | @Override 21 | public String getDataType(String type) { 22 | return switch (type) { 23 | case "integer" -> "integer"; 24 | case "bigint" -> "integer"; 25 | case "text" -> "text"; 26 | case "varchar" -> "text"; 27 | default -> type; 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: [ "1.21.11-dev" ] 6 | 7 | permissions: 8 | contents: read 9 | checks: write 10 | pull-requests: write 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | env: 18 | CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Set up JDK 21 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '21' 28 | distribution: 'temurin' 29 | 30 | - name: Grant Permissions to gradlew 31 | run: chmod +x gradlew 32 | 33 | - name: Test 34 | run: ./gradlew :common:test 35 | 36 | - name: Publish Test Results 37 | uses: EnricoMi/publish-unit-test-result-action@v2 38 | if: always() 39 | with: 40 | files: | 41 | common/build/test-results/**/*.xml 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/ExcludeFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.mojang.brigadier.StringReader; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 7 | import net.minecraft.world.item.Item; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class ExcludeFilter extends ItemFilter { 13 | 14 | public ExcludeFilter() { 15 | super(); 16 | } 17 | 18 | public ExcludeFilter(List items) { 19 | super(items); 20 | } 21 | 22 | @Override 23 | public String getName() { 24 | return "exclude"; 25 | } 26 | 27 | @Override 28 | public IFilter parse(StringReader reader, String suffix) throws CommandSyntaxException { 29 | return new ExcludeFilter(getItemsFromSuffix(reader, suffix)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/action/IAction.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.action; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.Operation; 5 | import net.minecraft.ChatFormatting; 6 | import net.minecraft.network.chat.Component; 7 | 8 | public interface IAction { 9 | 10 | String name(); 11 | int getId(); 12 | Operation getOperation(); 13 | 14 | default Component getPrefix() { 15 | return switch (getOperation()) { 16 | case ADD -> GriefLogger.translate("action.prefix.add").withStyle(ChatFormatting.GREEN); 17 | case REMOVE -> GriefLogger.translate("action.prefix.remove").withStyle(ChatFormatting.RED); 18 | case NEUTRAL -> GriefLogger.translate("action.prefix.neutral"); 19 | }; 20 | } 21 | 22 | default Component getPastTense() { 23 | return GriefLogger.translate("action." + this.toString().toLowerCase() + ".past"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/RemoveDoorInteractionsEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.block.BlockHandler; 4 | import com.daqem.grieflogger.database.service.Services; 5 | import com.daqem.grieflogger.event.AbstractEvent; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.level.Level; 8 | import net.minecraft.world.level.block.state.BlockState; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class RemoveDoorInteractionsEvent extends AbstractEvent { 14 | 15 | public static void removeDoorInteractions(Level level, BlockPos pos, BlockState state) { 16 | List positions = new ArrayList<>(List.of(pos)); 17 | BlockHandler.getSecondDoorPosition(pos, state).ifPresent(positions::add); 18 | for (BlockPos position : positions) { 19 | Services.BLOCK.removeInteractionsForPosition(level, position); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/BlockEvents.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import dev.architectury.event.EventResult; 4 | import dev.architectury.event.events.common.BlockEvent; 5 | import dev.architectury.event.events.common.InteractionEvent; 6 | 7 | public class BlockEvents { 8 | 9 | public static void registerEvents() { 10 | BlockEvent.BREAK.register(BreakBlockEvent::breakBlock); 11 | BlockEvent.PLACE.register(((level, blockPos, blockState, entity) -> { 12 | PlaceBlockEvent.placeBlock(level, blockPos, blockState, entity); 13 | return EventResult.pass(); 14 | })); 15 | InteractionEvent.LEFT_CLICK_BLOCK.register((player, hand, pos, direction) -> LeftClickBlockEvent.leftClickBlock(player, hand, pos, direction).asMinecraft()); 16 | InteractionEvent.RIGHT_CLICK_BLOCK.register((player, hand, pos, direction) -> RightClickBlockEvent.rightClickBlock(player, hand, pos, direction).asMinecraft()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/CommandEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import dev.architectury.event.EventResult; 5 | import dev.architectury.event.events.common.CommandPerformEvent; 6 | import net.minecraft.server.level.ServerPlayer; 7 | 8 | public class CommandEvent { 9 | 10 | public static void registerEvent() { 11 | CommandPerformEvent.EVENT.register(commandPerformEvent -> { 12 | ServerPlayer player = commandPerformEvent.getResults().getContext().getSource().getPlayer(); 13 | if (player != null) { 14 | Services.COMMAND.insert( 15 | player.getUUID(), 16 | player.level(), 17 | player.getOnPos(), 18 | commandPerformEvent.getResults().getReader().getString() 19 | ); 20 | } 21 | return EventResult.pass(); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/PlayerJoinEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.action.SessionAction; 5 | import com.mojang.authlib.GameProfile; 6 | import dev.architectury.event.events.common.PlayerEvent; 7 | 8 | import java.util.UUID; 9 | 10 | public class PlayerJoinEvent { 11 | 12 | public static void registerEvent() { 13 | PlayerEvent.PLAYER_JOIN.register(player -> { 14 | GameProfile gameProfile = player.getGameProfile(); 15 | UUID uuid = gameProfile.id(); 16 | 17 | Services.USER.insertOrUpdateName( 18 | uuid, 19 | gameProfile.name() 20 | ); 21 | 22 | Services.SESSION.insert( 23 | uuid, 24 | player.level(), 25 | player.getOnPos(), 26 | SessionAction.JOIN 27 | ); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/mixin/ItemMixin.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.mixin; 2 | 3 | import com.daqem.grieflogger.event.item.ConsumeItemEvent; 4 | import net.minecraft.world.entity.LivingEntity; 5 | import net.minecraft.world.entity.player.Player; 6 | import net.minecraft.world.item.Item; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.level.Level; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(Item.class) 15 | public class ItemMixin { 16 | 17 | @Inject(at = @At("RETURN"), method = "finishUsingItem") 18 | public void onUse(ItemStack itemStack, Level level, LivingEntity livingEntity, CallbackInfoReturnable cir) { 19 | if (livingEntity instanceof Player player) { 20 | ConsumeItemEvent.consumeItem(player, itemStack); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/player/GriefLoggerServerPlayer.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.player; 2 | 3 | import com.daqem.grieflogger.command.page.Page; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import com.daqem.grieflogger.model.history.BlockHistory; 7 | import com.daqem.grieflogger.model.history.ContainerHistory; 8 | import com.daqem.grieflogger.model.history.IHistory; 9 | import net.minecraft.server.level.ServerPlayer; 10 | 11 | import java.util.List; 12 | 13 | public interface GriefLoggerServerPlayer extends GriefLoggerPlayer { 14 | 15 | boolean grieflogger$isInspecting(); 16 | void grieflogger$setInspecting(boolean inspecting); 17 | void grieflogger$sendInspectMessage(List history); 18 | void griefLogger$addItemToQueue(ItemAction action, SimpleItemStack itemStack); 19 | ServerPlayer grieflogger$asServerPlayer(); 20 | List grieflogger$getPages(); 21 | void grieflogger$setPages(List pages); 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/Services.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | 5 | public interface Services { 6 | 7 | BlockService BLOCK = new BlockService(GriefLogger.getDatabase()); 8 | ChatService CHAT = new ChatService(GriefLogger.getDatabase()); 9 | CommandService COMMAND = new CommandService(GriefLogger.getDatabase()); 10 | ContainerService CONTAINER = new ContainerService(GriefLogger.getDatabase()); 11 | EntityService ENTITY = new EntityService(GriefLogger.getDatabase()); 12 | ItemService ITEM = new ItemService(GriefLogger.getDatabase()); 13 | LevelService LEVEL = new LevelService(GriefLogger.getDatabase()); 14 | MaterialService MATERIAL = new MaterialService(GriefLogger.getDatabase()); 15 | SessionService SESSION = new SessionService(GriefLogger.getDatabase()); 16 | UsernameService USERNAME = new UsernameService(GriefLogger.getDatabase()); 17 | UserService USER = new UserService(GriefLogger.getDatabase()); 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/action/Actions.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.action; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | public interface Actions { 10 | 11 | List ACTIONS = Stream.of( 12 | BlockAction.values(), 13 | SessionAction.values(), 14 | ItemAction.values() 15 | ) 16 | .flatMap(Arrays::stream) 17 | .collect(Collectors.toList()); 18 | 19 | static IAction getAction(String str) { 20 | return ACTIONS.stream() 21 | .filter(action -> action.name().equalsIgnoreCase(str)) 22 | .findFirst() 23 | .orElse(null); 24 | } 25 | 26 | static List getActions(String[] split) { 27 | return ACTIONS.stream() 28 | .filter(action -> Arrays.asList(split).contains(action.name().toLowerCase())) 29 | .collect(Collectors.toList()); 30 | } 31 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.UserRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | 7 | import java.util.Map; 8 | import java.util.UUID; 9 | 10 | public class UserService { 11 | 12 | private final UserRepository userRepository; 13 | private final UsernameService usernameService; 14 | 15 | public UserService(Database database) { 16 | this.userRepository = new UserRepository(database); 17 | this.usernameService = new UsernameService(database); 18 | } 19 | 20 | public void createTable() { 21 | userRepository.createTable(); 22 | } 23 | 24 | public void insertOrUpdateName(UUID uuid, String name) { 25 | userRepository.insertOrUpdateName(name,uuid.toString()); 26 | usernameService.insert(uuid, name); 27 | } 28 | 29 | public Map getAllUsernames() { 30 | return userRepository.getAllUsernames(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/action/BlockAction.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.action; 2 | 3 | import com.daqem.grieflogger.model.Operation; 4 | 5 | public enum BlockAction implements IAction { 6 | BREAK_BLOCK(0, Operation.REMOVE), 7 | PLACE_BLOCK(1, Operation.ADD), 8 | INTERACT_BLOCK(2, Operation.NEUTRAL), 9 | KILL_ENTITY(3, Operation.REMOVE), 10 | INTERACT_ENTITY(4, Operation.NEUTRAL); 11 | 12 | private final int id; 13 | private final Operation operation; 14 | 15 | BlockAction(int id, Operation operation) { 16 | this.id = id; 17 | this.operation = operation; 18 | } 19 | 20 | @Override 21 | public int getId() { 22 | return id; 23 | } 24 | 25 | @Override 26 | public Operation getOperation() { 27 | return operation; 28 | } 29 | 30 | public static BlockAction fromId(int id) { 31 | for (BlockAction blockAction : BlockAction.values()) { 32 | if (blockAction.getId() == id) { 33 | return blockAction; 34 | } 35 | } 36 | return null; 37 | } 38 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/IncludeFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.mojang.brigadier.StringReader; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 7 | import net.minecraft.core.Registry; 8 | import net.minecraft.core.registries.BuiltInRegistries; 9 | import net.minecraft.core.registries.Registries; 10 | import net.minecraft.world.item.Item; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class IncludeFilter extends ItemFilter { 16 | 17 | public IncludeFilter() { 18 | super(); 19 | } 20 | 21 | public IncludeFilter(List items) { 22 | super(items); 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return "include"; 28 | } 29 | 30 | @Override 31 | public IFilter parse(StringReader reader, String suffix) throws CommandSyntaxException { 32 | return new IncludeFilter(getItemsFromSuffix(reader, suffix)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /relocate_natives/fix_modified_binary.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import lief 3 | import subprocess 4 | import download_codesign 5 | from pathlib import Path 6 | 7 | # Parse the input binary & xit if binary is invalid 8 | output_path = sys.argv[1] 9 | binary = lief.parse(sys.stdin.buffer.read()) 10 | if binary is None: 11 | exit(1) 12 | 13 | # Remove signature from Mac binaries 14 | if isinstance(binary, lief.MachO.Binary): 15 | binary.remove_signature() 16 | 17 | # Write the modified binary to the output path 18 | binary.write(output_path) 19 | 20 | # Sign Mac binaries (required to make them usable because apple) 21 | if isinstance(binary, lief.MachO.Binary): 22 | print(f"Signing {output_path}...") 23 | 24 | # Check if the Apple code-signing files are available, if not, download them 25 | if not Path("./apple-codesign/COPYING").exists(): 26 | download_codesign.download_and_unpack() 27 | 28 | # Run the code-signing process 29 | sign_process = subprocess.Popen(["./apple-codesign/rcodesign", "sign", output_path], shell=False, 30 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 31 | sign_process.wait() 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/BreakContainerEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.block.container.ContainerHandler; 4 | import com.daqem.grieflogger.database.service.Services; 5 | import com.daqem.grieflogger.model.SimpleItemStack; 6 | import com.daqem.grieflogger.model.action.ItemAction; 7 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; 11 | 12 | import java.util.List; 13 | 14 | public class BreakContainerEvent { 15 | 16 | public static void breakContainer(GriefLoggerServerPlayer player, Level level, BlockPos pos, BaseContainerBlockEntity containerBlockEntity) { 17 | List itemStacks = ContainerHandler.getContainerItems(containerBlockEntity); 18 | Services.CONTAINER.insertList( 19 | player.grieflogger$asServerPlayer().getUUID(), 20 | level, 21 | pos, 22 | itemStacks, 23 | ItemAction.REMOVE_ITEM); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/LogBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.event.AbstractEvent; 5 | import com.daqem.grieflogger.model.action.BlockAction; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.resources.Identifier; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraft.world.level.block.state.BlockState; 11 | 12 | public class LogBlockEvent extends AbstractEvent { 13 | 14 | public static void logBlock(GriefLoggerServerPlayer player, Level level, BlockState state, BlockPos pos, BlockAction blockAction) { 15 | Identifier materialLocation = state.getBlock().arch$registryName(); 16 | if (materialLocation != null) { 17 | Services.BLOCK.insertMaterial( 18 | player.grieflogger$asServerPlayer().getUUID(), 19 | level.dimension().identifier().toString(), 20 | pos, 21 | materialLocation.toString(), 22 | blockAction); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096M 2 | 3 | enabled_platforms=fabric,neoforge 4 | maven_group=com.daqem.grieflogger 5 | archives_base_name=grieflogger 6 | 7 | # Project 8 | mod_version=19.0.0 9 | mod_id=grieflogger 10 | mod_name=GriefLogger 11 | mod_description=A mod that logs all player interactions with blocks and entities and stores them in a database. 12 | mod_author=DAQEM 13 | mod_license=Apache-2.0 14 | mod_website=https://daqem.com 15 | mod_repository=https://github.com/DAQEM/GriefLogger 16 | 17 | # Publishing 18 | supported_minecraft_versions=1.21.11 19 | release_type=release 20 | 21 | curse_forge_project_id=435029 22 | curseforge_dependencies=architectury-api,yaml-config 23 | 24 | modrinth_project_id=8oGVUFuX 25 | modrinth_dependencies=architectury-api,yaml-config 26 | 27 | # Minecraft 28 | minecraft_version=1.21.11 29 | 30 | # Fabric 31 | fabric_loader_version=0.18.3 32 | fabric_api_version=0.140.0+1.21.11 33 | 34 | # NeoForge 35 | neoforge_version=21.11.0-beta 36 | neoforge_loader_version=1 37 | 38 | # Dependencies 39 | architectury_version=19.0.1 40 | config_library_version=19.0.0 41 | sqlite_version=3.47.2.0 42 | mysql_version=8.4.0 43 | 44 | # Runtime Only 45 | ui_library_version=19.0.0 46 | 47 | 48 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.ChatRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.level.Level; 8 | 9 | import java.util.UUID; 10 | 11 | public class ChatService { 12 | 13 | private final ChatRepository chatRepository; 14 | 15 | public ChatService(Database database) { 16 | this.chatRepository = new ChatRepository(database); 17 | } 18 | 19 | public void createTable() { 20 | chatRepository.createTable(); 21 | } 22 | 23 | public void createIndexes() { 24 | chatRepository.createIndexes(); 25 | } 26 | 27 | public void insert(UUID userUuid, Level level, BlockPos pos, String message) { 28 | chatRepository.insert(System.currentTimeMillis(), 29 | userUuid.toString(), 30 | level.dimension().identifier().toString(), 31 | pos.getX(), 32 | pos.getY(), 33 | pos.getZ(), 34 | message); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/action/SessionAction.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.action; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.Operation; 5 | import net.minecraft.network.chat.Component; 6 | 7 | public enum SessionAction implements IAction { 8 | JOIN(0, Operation.ADD), 9 | QUIT(1, Operation.REMOVE); 10 | 11 | private final int id; 12 | private final Operation operation; 13 | 14 | SessionAction(int id, Operation operation) { 15 | this.id = id; 16 | this.operation = operation; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | @Override 24 | public Operation getOperation() { 25 | return operation; 26 | } 27 | 28 | public static SessionAction fromId(int id) { 29 | for (SessionAction sessionAction : SessionAction.values()) { 30 | if (sessionAction.getId() == id) { 31 | return sessionAction; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public Component getPastTense() { 39 | return GriefLogger.translate("action." + this.name().toLowerCase() + ".past"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[${neoforge_loader_version},)" 3 | issueTrackerURL = "${mod_repository}/issues" 4 | license = "${mod_license}" 5 | 6 | [[mods]] 7 | modId = "${mod_id}" 8 | version = "${version}" 9 | license = "${mod_license}" 10 | displayName = "${mod_name}" 11 | authors = "${mod_author}" 12 | credits = "${mod_author}" 13 | description = "${mod_description}" 14 | 15 | [[dependencies."${mod_id}"]] 16 | modId = "neoforge" 17 | type = "required" 18 | versionRange = "[21,)" 19 | ordering = "NONE" 20 | side = "SERVER" 21 | 22 | [[dependencies."${mod_id}"]] 23 | modId = "minecraft" 24 | type = "required" 25 | versionRange = "[${minecraft_version},)" 26 | ordering = "NONE" 27 | side = "SERVER" 28 | 29 | [[dependencies."${mod_id}"]] 30 | modId = "architectury" 31 | type = "required" 32 | versionRange = "[${architectury_version},)" 33 | ordering = "AFTER" 34 | side = "SERVER" 35 | 36 | [[dependencies."${mod_id}"]] 37 | modId = "yamlconfig" 38 | type = "required" 39 | versionRange = "[${config_library_version},)" 40 | ordering = "AFTER" 41 | side = "SERVER" 42 | 43 | [[mixins]] 44 | config = "grieflogger-common.mixins.json" 45 | 46 | [[mixins]] 47 | config = "grieflogger-neoforge.mixins.json" -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/CommandService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.database.Database; 4 | import com.daqem.grieflogger.database.repository.CommandRepository; 5 | import com.daqem.grieflogger.thread.ThreadManager; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.world.level.Level; 8 | 9 | import java.util.UUID; 10 | 11 | public class CommandService { 12 | 13 | private final CommandRepository commandRepository; 14 | 15 | public CommandService(Database database) { 16 | this.commandRepository = new CommandRepository(database); 17 | } 18 | 19 | public void createTable() { 20 | commandRepository.createTable(); 21 | } 22 | 23 | public void createIndexes() { 24 | commandRepository.createIndexes(); 25 | } 26 | 27 | public void insert(UUID userUuid, Level level, BlockPos pos, String command) { 28 | commandRepository.insert(System.currentTimeMillis(), 29 | userUuid.toString(), 30 | level.dimension().identifier().toString(), 31 | pos.getX(), 32 | pos.getY(), 33 | pos.getZ(), 34 | command); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/GriefLoggerCommand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command; 2 | 3 | import com.daqem.grieflogger.GriefLoggerPermissions; 4 | import com.mojang.brigadier.CommandDispatcher; 5 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 6 | import net.minecraft.commands.CommandSourceStack; 7 | import net.minecraft.commands.Commands; 8 | 9 | public class GriefLoggerCommand { 10 | 11 | private static final ICommand INSPECT = new InspectCommand(); 12 | private static final ICommand LOOKUP = new LookupCommand(); 13 | private static final ICommand PAGE = new PageCommand(); 14 | 15 | public static void registerCommand(CommandDispatcher dispatcher) { 16 | dispatcher.register(commandWithPrefix("grieflogger")); 17 | dispatcher.register(commandWithPrefix("gl")); 18 | } 19 | 20 | private static LiteralArgumentBuilder commandWithPrefix(String prefix) { 21 | return Commands.literal(prefix) 22 | .requires(source -> GriefLoggerPermissions.check(source, "grieflogger.command", 2)) 23 | .then(INSPECT.getCommand()) 24 | .then(LOOKUP.getCommand()) 25 | .then(PAGE.getCommand()); 26 | } 27 | } -------------------------------------------------------------------------------- /fabric/src/main/java/com/daqem/grieflogger/fabric/GriefLoggerPermissionsImpl.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.fabric; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import net.minecraft.commands.CommandSourceStack; 5 | import net.minecraft.server.permissions.Permission; 6 | import net.minecraft.server.permissions.PermissionLevel; 7 | import net.minecraft.server.permissions.Permissions; 8 | 9 | public class GriefLoggerPermissionsImpl { 10 | 11 | private static final boolean PERMISSIONS_API_LOADED = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0"); 12 | 13 | public static boolean check(CommandSourceStack source, String permissionNode, int fallbackLevel) { 14 | if (PERMISSIONS_API_LOADED) { 15 | try { 16 | return me.lucko.fabric.api.permissions.v0.Permissions.check(source, permissionNode, fallbackLevel); 17 | } catch (Throwable t) { 18 | // Fallback if something goes wrong with the API 19 | return source.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.byId(fallbackLevel))); 20 | } 21 | } 22 | return source.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.byId(fallbackLevel))); 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/InspectCommand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.GriefLoggerPermissions; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 7 | import net.minecraft.commands.CommandSourceStack; 8 | import net.minecraft.commands.Commands; 9 | 10 | public class InspectCommand implements ICommand { 11 | 12 | @Override 13 | public LiteralArgumentBuilder getCommand() { 14 | return Commands.literal("inspect") 15 | .requires(source -> GriefLoggerPermissions.check(source, "grieflogger.command.inspect", 2)) 16 | .executes(context -> inspect(context.getSource())); 17 | } 18 | 19 | private static int inspect(CommandSourceStack source) { 20 | if (source.getPlayer() instanceof GriefLoggerServerPlayer player) { 21 | player.grieflogger$setInspecting(!player.grieflogger$isInspecting()); 22 | source.sendSuccess(() -> GriefLogger.translate("commands.inspect." + (player.grieflogger$isInspecting() ? "enabled" : "disabled"), GriefLogger.getName()), false); 23 | } 24 | return 1; 25 | } 26 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/ContainerHistory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.BlockPosition; 5 | import com.daqem.grieflogger.model.SimpleItemStack; 6 | import com.daqem.grieflogger.model.Time; 7 | import com.daqem.grieflogger.model.User; 8 | import com.daqem.grieflogger.model.action.IAction; 9 | import com.daqem.grieflogger.model.action.ItemAction; 10 | import net.minecraft.core.component.DataComponentPatch; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.network.chat.HoverEvent; 13 | import net.minecraft.network.chat.MutableComponent; 14 | import net.minecraft.resources.Identifier; 15 | 16 | import java.util.UUID; 17 | 18 | public class ContainerHistory extends ItemHistory { 19 | 20 | public ContainerHistory(long time, String name, String uuid, int x, int y, int z, String material, DataComponentPatch data, int amount, int action) { 21 | super(time, name, uuid, x, y, z, material, data, amount, action); 22 | } 23 | 24 | public ContainerHistory(Time time, User user, BlockPosition position, SimpleItemStack itemStack, IAction action) { 25 | super(time, user, position, itemStack, action); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/TimeUnit.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.minecraft.network.chat.Component; 5 | 6 | import java.util.List; 7 | 8 | public enum TimeUnit { 9 | MINUTE(60 * 1000L, GriefLogger.translate("time.minutes")), 10 | HOUR(60 * MINUTE.getMilliseconds(), GriefLogger.translate("time.hours")), 11 | DAY(24 * HOUR.getMilliseconds(), GriefLogger.translate("time.days")), 12 | YEAR(365 * DAY.getMilliseconds(), GriefLogger.translate("time.years")); 13 | 14 | private final long milliseconds; 15 | private final Component component; 16 | 17 | TimeUnit(long milliseconds, Component component) { 18 | this.milliseconds = milliseconds; 19 | this.component = component; 20 | } 21 | 22 | public long getMilliseconds() { 23 | return milliseconds; 24 | } 25 | 26 | public Component getComponent() { 27 | return component; 28 | } 29 | 30 | public static List getAbbreviations() { 31 | return List.of( 32 | MINUTE.component.getString(), 33 | HOUR.component.getString(), 34 | DAY.component.getString(), 35 | YEAR.component.getString() 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/History.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import com.daqem.grieflogger.model.BlockPosition; 4 | import com.daqem.grieflogger.model.Time; 5 | import com.daqem.grieflogger.model.User; 6 | import com.daqem.grieflogger.model.action.IAction; 7 | import net.minecraft.network.chat.Component; 8 | 9 | public abstract class History implements IHistory { 10 | 11 | private final Time time; 12 | private final User user; 13 | private final BlockPosition position; 14 | private final IAction action; 15 | 16 | public History(Time time, User user, BlockPosition position, IAction action) { 17 | this.time = time; 18 | this.user = user; 19 | this.position = position; 20 | this.action = action; 21 | } 22 | 23 | public Time getTime() { 24 | return time; 25 | } 26 | 27 | public User getUser() { 28 | return user; 29 | } 30 | 31 | public BlockPosition getPosition() { 32 | return position; 33 | } 34 | 35 | public IAction getAction() { 36 | return action; 37 | } 38 | 39 | @Override 40 | public Component getComponentWithPos() { 41 | return getComponent().copy().append(" ").append(getPosition().getComponent()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/action/ItemAction.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.action; 2 | 3 | import com.daqem.grieflogger.model.Operation; 4 | 5 | public enum ItemAction implements IAction { 6 | REMOVE_ITEM(0, Operation.REMOVE), 7 | ADD_ITEM(1, Operation.ADD), 8 | DROP_ITEM(2, Operation.REMOVE), 9 | PICKUP_ITEM(3, Operation.ADD), 10 | CRAFT_ITEM(4, Operation.ADD), 11 | BREAK_ITEM(5, Operation.REMOVE), 12 | CONSUME_ITEM(6, Operation.REMOVE), 13 | THROW_ITEM(7, Operation.REMOVE), 14 | SHOOT_ITEM(8, Operation.REMOVE), 15 | ADD_ITEM_ENDER(9, Operation.ADD), 16 | REMOVE_ITEM_ENDER(10, Operation.REMOVE); 17 | 18 | private final int id; 19 | private final Operation operation; 20 | 21 | ItemAction(int id, Operation operation) { 22 | this.id = id; 23 | this.operation = operation; 24 | } 25 | 26 | @Override 27 | public int getId() { 28 | return id; 29 | } 30 | 31 | @Override 32 | public Operation getOperation() { 33 | return operation; 34 | } 35 | 36 | public static ItemAction fromId(int id) { 37 | for (ItemAction itemAction : ItemAction.values()) { 38 | if (itemAction.getId() == id) { 39 | return itemAction; 40 | } 41 | } 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | architectury { 2 | common(rootProject.enabled_platforms.split(",")) 3 | } 4 | 5 | loom { 6 | accessWidenerPath = file("src/main/resources/grieflogger.accesswidener") 7 | } 8 | 9 | dependencies { 10 | // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies 11 | // Do NOT use other classes from fabric loader 12 | modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" 13 | // Remove the next line if you don't want to depend on the API 14 | modApi "dev.architectury:architectury:${rootProject.architectury_version}" 15 | 16 | modImplementation "com.daqem.yamlconfig:yamlconfig-common:${project.config_library_version}" 17 | } 18 | 19 | publishing { 20 | publications { 21 | mavenFabric(MavenPublication) { 22 | groupId = rootProject.maven_group 23 | artifactId = rootProject.archives_base_name + "-common" 24 | from components.java 25 | } 26 | } 27 | 28 | repositories { 29 | maven { 30 | url = project.version.contains('-PR') ? 'https://maven.daqem.com/snapshots' : 'https://maven.daqem.com/releases' 31 | credentials { 32 | username = System.getenv("MAVEN_USER") 33 | password = System.getenv("MAVEN_PASS") 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/SessionHistory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.BlockPosition; 5 | import com.daqem.grieflogger.model.Time; 6 | import com.daqem.grieflogger.model.User; 7 | import com.daqem.grieflogger.model.action.IAction; 8 | import com.daqem.grieflogger.model.action.ItemAction; 9 | import com.daqem.grieflogger.model.action.SessionAction; 10 | import net.minecraft.network.chat.Component; 11 | 12 | import java.util.UUID; 13 | 14 | public class SessionHistory extends History { 15 | 16 | public SessionHistory(long time, String name, String uuid, int x, int y, int z, int sessionAction) { 17 | this(new Time(time), new User(name, UUID.fromString(uuid)), new BlockPosition(x, y, z), SessionAction.fromId(sessionAction)); 18 | } 19 | 20 | public SessionHistory(Time time, User user, BlockPosition position, IAction action) { 21 | super(time, user, position, action); 22 | } 23 | 24 | @Override 25 | public Component getComponent() { 26 | return getTime().getFormattedTimeAgo().append(" ") 27 | .append(getAction().getPrefix()).append(" ") 28 | .append(getUser().getNameComponent()).append(" ") 29 | .append(getAction().getPastTense()); 30 | } 31 | 32 | @Override 33 | public Component getMaterialComponent() { 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/InspectDoorEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.block.BlockHandler; 4 | import com.daqem.grieflogger.database.service.Services; 5 | import com.daqem.grieflogger.event.AbstractEvent; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import dev.architectury.event.EventResult; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraft.world.level.block.state.BlockState; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class InspectDoorEvent extends AbstractEvent { 16 | 17 | public static EventResult inspectDoor(GriefLoggerServerPlayer player, Level level, BlockPos pos, BlockState state, boolean isInteraction) { 18 | List positions = new ArrayList<>(List.of(pos)); 19 | BlockHandler.getSecondDoorPosition(pos, state).ifPresent(positions::add); 20 | if (isInteraction) { 21 | Services.BLOCK.getInteractionHistoryAsync( 22 | level, 23 | positions, 24 | player::grieflogger$sendInspectMessage); 25 | return interrupt(); 26 | } else { 27 | Services.BLOCK.getBlockHistoryAsync( 28 | level, 29 | positions, 30 | player::grieflogger$sendInspectMessage); 31 | } 32 | return interrupt(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fabric/src/main/java/com/daqem/grieflogger/fabric/mixin/MixinBucketItem.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.fabric.mixin; 2 | 3 | import com.daqem.grieflogger.event.block.PlaceBlockEvent; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.item.BucketItem; 8 | import net.minecraft.world.level.Level; 9 | import net.minecraft.world.phys.BlockHitResult; 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(BucketItem.class) 16 | public class MixinBucketItem { 17 | 18 | @Inject( 19 | method = "emptyContents", 20 | at = @At( 21 | value = "INVOKE", 22 | target = "Lnet/minecraft/world/level/Level;setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z", 23 | shift = At.Shift.AFTER 24 | ) 25 | ) 26 | private void onLiquidPlaced(LivingEntity livingEntity, Level level, BlockPos blockPos, BlockHitResult blockHitResult, CallbackInfoReturnable cir) { 27 | if (livingEntity instanceof ServerPlayer serverPlayer) { 28 | PlaceBlockEvent.placeBlock(level, blockPos, level.getBlockState(blockPos), serverPlayer); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/mixin/ProjectileMixin.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.mixin; 2 | 3 | import com.daqem.grieflogger.event.item.ShootItemEvent; 4 | import com.daqem.grieflogger.event.item.ThrowItemEvent; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.entity.projectile.arrow.AbstractArrow; 8 | import net.minecraft.world.entity.projectile.Projectile; 9 | import net.minecraft.world.entity.projectile.throwableitemprojectile.ThrowableItemProjectile; 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(Projectile.class) 16 | public class ProjectileMixin { 17 | 18 | @Inject(at = @At("HEAD"), method = "shootFromRotation(Lnet/minecraft/world/entity/Entity;FFFFF)V") 19 | private void shootFromRotation(Entity entity, float f, float g, float h, float i, float j, CallbackInfo ci) { 20 | if (entity instanceof Player player) { 21 | if ((Projectile) (Object) this instanceof ThrowableItemProjectile throwableItemProjectile) { 22 | ThrowItemEvent.throwItem(player, throwableItemProjectile.getItem()); 23 | } else if ((Projectile) (Object) this instanceof AbstractArrow abstractArrow) { 24 | ShootItemEvent.shootItem(player, abstractArrow.getPickupItem()); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/Filters.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import java.util.List; 4 | 5 | public interface Filters { 6 | 7 | IFilter ACTION = new ActionFilter(); 8 | IFilter EXCLUDE = new ExcludeFilter(); 9 | IFilter INCLUDE = new IncludeFilter(); 10 | IFilter RADIUS = new RadiusFilter(); 11 | IFilter TIME = new TimeFilter(); 12 | IFilter USER = new UserFilter(); 13 | 14 | List FILTERS = List.of(ACTION, EXCLUDE, INCLUDE, RADIUS, TIME, USER); 15 | 16 | static IFilter fromPrefix(String prefix) { 17 | if (prefix == null || prefix.isEmpty()) { 18 | return null; 19 | } 20 | if (prefix.length() == 1) { 21 | return FILTERS.stream() 22 | .filter(x -> x.getPrefix() == prefix.charAt(0)) 23 | .findFirst() 24 | .orElse(null); 25 | } 26 | return FILTERS.stream() 27 | .filter(x -> x.getName().toLowerCase().startsWith(prefix)) 28 | .findFirst() 29 | .orElse(null); 30 | } 31 | 32 | static String[] getFilteredSuggestions(List filters, boolean hasItemFilter) { 33 | return FILTERS.stream() 34 | .filter(x -> !hasItemFilter || !(x instanceof ItemFilter)) 35 | .filter(x -> filters.stream().noneMatch(y -> y.getClass() == x.getClass())) 36 | .map(x -> x.getName() + '.') 37 | .toArray(String[]::new); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/EntityEvents.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.action.BlockAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import dev.architectury.event.EventResult; 7 | import dev.architectury.event.events.common.EntityEvent; 8 | import dev.architectury.event.events.common.InteractionEvent; 9 | import net.minecraft.resources.Identifier; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.world.InteractionHand; 12 | import net.minecraft.world.entity.decoration.ArmorStand; 13 | 14 | import java.util.List; 15 | 16 | public class EntityEvents { 17 | 18 | public static void registerEvents() { 19 | EntityEvent.LIVING_DEATH.register((entity, source) -> { 20 | if (source != null && source.getEntity() instanceof ServerPlayer serverPlayer) { 21 | Identifier entityLocation = entity.getType().arch$registryName(); 22 | if (entityLocation != null) { 23 | Services.BLOCK.insertEntity( 24 | serverPlayer.getUUID(), 25 | entity.level().dimension().identifier().toString(), 26 | entity.blockPosition(), 27 | entityLocation.toString(), 28 | BlockAction.KILL_ENTITY 29 | ); 30 | } 31 | } 32 | return EventResult.pass(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/LeftClickBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.event.AbstractEvent; 4 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 5 | import dev.architectury.event.EventResult; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.core.Direction; 8 | import net.minecraft.world.InteractionHand; 9 | import net.minecraft.world.entity.player.Player; 10 | import net.minecraft.world.level.Level; 11 | import net.minecraft.world.level.block.Block; 12 | import net.minecraft.world.level.block.DoorBlock; 13 | import net.minecraft.world.level.block.state.BlockState; 14 | 15 | public class LeftClickBlockEvent extends AbstractEvent { 16 | 17 | public static EventResult leftClickBlock(Player player, InteractionHand hand, BlockPos pos, Direction direction) { 18 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 19 | if (hand == InteractionHand.MAIN_HAND) { 20 | if (serverPlayer.grieflogger$isInspecting()) { 21 | 22 | Level level = player.level(); 23 | BlockState state = level.getBlockState(pos); 24 | Block block = state.getBlock(); 25 | 26 | if (block instanceof DoorBlock) { 27 | return InspectDoorEvent.inspectDoor(serverPlayer, level, pos, state, false); 28 | } 29 | return InspectBlockEvent.inspectBlock(serverPlayer, pos); 30 | } 31 | } 32 | } 33 | return pass(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/thread/ThreadManager.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.thread; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.Callable; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | import java.util.concurrent.Future; 9 | import java.util.stream.Collectors; 10 | 11 | 12 | public class ThreadManager { 13 | 14 | private static final ExecutorService executor = Executors.newFixedThreadPool( 15 | Runtime.getRuntime().availableProcessors(), 16 | new GriefLoggerThreadFactory() 17 | ); 18 | private static final Map, OnComplete> onCompleteMap = new HashMap<>(); 19 | 20 | public static void execute(Runnable runnable) { 21 | executor.execute(runnable); 22 | } 23 | 24 | public static void submit(Callable task, OnComplete onComplete) { 25 | Future future = executor.submit(task); 26 | onCompleteMap.put(future, onComplete); 27 | } 28 | 29 | public static Map, OnComplete> getAndRemoveCompleted() { 30 | //noinspection unchecked 31 | Map, OnComplete> completedFutures = onCompleteMap.entrySet().stream() 32 | .filter(entry -> entry.getKey().isDone()) 33 | .collect(Collectors.toMap( 34 | entry -> (Future) entry.getKey(), 35 | entry -> (OnComplete) entry.getValue() 36 | )); 37 | completedFutures.forEach(onCompleteMap::remove); 38 | return completedFutures; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/LevelRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class LevelRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public LevelRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS levels (" + 20 | "id " + database.getDialect().getDataType("integer") + " PRIMARY KEY" + (database.getDialect() instanceof MySQLDialect ? " AUTO_INCREMENT" : "") + "," + 21 | "name " + database.getDialect().getDataType("text") + " NOT NULL UNIQUE" + 22 | ")"; 23 | if (database.getDialect() instanceof MySQLDialect) { 24 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 25 | } else { 26 | sql += ";"; 27 | } 28 | database.createTable(sql); 29 | } 30 | 31 | public void insert(String name) { 32 | String query = database.getDialect().getInsertIgnore() + " INTO levels(name) VALUES(?);"; 33 | 34 | database.queue.add(connection -> { 35 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 36 | preparedStatement.setString(1, name); 37 | preparedStatement.executeUpdate(); 38 | } 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/EntityRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class EntityRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public EntityRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS entities (" + 20 | "id " + database.getDialect().getDataType("integer") + " PRIMARY KEY" + (database.getDialect() instanceof MySQLDialect ? " AUTO_INCREMENT" : "") + "," + 21 | "name " + database.getDialect().getDataType("text") + " NOT NULL UNIQUE" + 22 | ")"; 23 | if (database.getDialect() instanceof MySQLDialect) { 24 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 25 | } else { 26 | sql += ";"; 27 | } 28 | database.createTable(sql); 29 | } 30 | 31 | public void insert(String name) { 32 | String query = database.getDialect().getInsertIgnore() + " INTO entities(name) VALUES(?);"; 33 | 34 | database.queue.add(connection -> { 35 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 36 | preparedStatement.setString(1, name); 37 | preparedStatement.executeUpdate(); 38 | } 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/MaterialRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class MaterialRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public MaterialRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS materials (" + 20 | "id " + database.getDialect().getDataType("integer") + " PRIMARY KEY" + (database.getDialect() instanceof MySQLDialect ? " AUTO_INCREMENT" : "") + "," + 21 | "name " + database.getDialect().getDataType("text") + " NOT NULL UNIQUE" + 22 | ")"; 23 | if (database.getDialect() instanceof MySQLDialect) { 24 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 25 | } else { 26 | sql += ";"; 27 | } 28 | database.createTable(sql); 29 | } 30 | 31 | public void insert(String material) { 32 | String query = database.getDialect().getInsertIgnore() + " INTO materials(name) VALUES(?);"; 33 | 34 | database.queue.add(connection -> { 35 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 36 | preparedStatement.setString(1, material); 37 | preparedStatement.executeUpdate(); 38 | } 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/queue/Queue.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.queue; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.database.Database; 5 | 6 | import java.sql.PreparedStatement; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.concurrent.ConcurrentLinkedQueue; 10 | 11 | public class Queue implements IQueue { 12 | 13 | private final Database database; 14 | private final boolean isBatch; 15 | private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); 16 | 17 | public Queue(Database database, boolean isBatch) { 18 | this.database = database; 19 | this.isBatch = isBatch; 20 | } 21 | 22 | @Override 23 | public void add(SqlTask task) { 24 | this.queue.add(task); 25 | } 26 | 27 | @Override 28 | public void execute() { 29 | if (this.queue.isEmpty()) { 30 | return; 31 | } 32 | List items = new ArrayList<>(); 33 | Object item; 34 | while ((item = this.queue.poll()) != null) { 35 | items.add(item); 36 | } 37 | this.database.executeQueue(items, isBatch); 38 | } 39 | 40 | @Override 41 | public void hello() { 42 | this.add(connection -> { 43 | try (PreparedStatement statement = connection.prepareStatement("SELECT 1")) { 44 | statement.execute(); 45 | } catch (Exception e) { 46 | GriefLogger.LOGGER.error("Failed to send hello packet", e); 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | public boolean isEmpty() { 53 | return this.queue.isEmpty(); 54 | } 55 | } -------------------------------------------------------------------------------- /neoforge/src/main/java/com/daqem/grieflogger/neoforge/mixin/MixinBucketItem.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.neoforge.mixin; 2 | 3 | import com.daqem.grieflogger.event.block.PlaceBlockEvent; 4 | import net.minecraft.core.BlockPos; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.item.BucketItem; 8 | import net.minecraft.world.item.ItemStack; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraft.world.phys.BlockHitResult; 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(BucketItem.class) 17 | public class MixinBucketItem { 18 | 19 | @Inject( 20 | method = "emptyContents(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/phys/BlockHitResult;Lnet/minecraft/world/item/ItemStack;)Z", 21 | at = @At( 22 | value = "INVOKE", 23 | target = "Lnet/minecraft/world/level/Level;setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z", 24 | shift = At.Shift.AFTER 25 | ) 26 | ) 27 | private void onLiquidPlaced(LivingEntity livingEntity, Level level, BlockPos blockPos, BlockHitResult blockHitResult, ItemStack itemStack, CallbackInfoReturnable cir) { 28 | if (livingEntity instanceof ServerPlayer serverPlayer) { 29 | PlaceBlockEvent.placeBlock(level, blockPos, level.getBlockState(blockPos), serverPlayer); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/PageCommand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.GriefLoggerPermissions; 5 | import com.daqem.grieflogger.command.page.Page; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import com.mojang.brigadier.arguments.IntegerArgumentType; 8 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 9 | import net.minecraft.commands.CommandSourceStack; 10 | import net.minecraft.commands.Commands; 11 | import net.minecraft.server.level.ServerPlayer; 12 | 13 | import java.util.List; 14 | 15 | public class PageCommand implements ICommand { 16 | 17 | @Override 18 | public LiteralArgumentBuilder getCommand() { 19 | return Commands.literal("page") 20 | .requires(source -> GriefLoggerPermissions.check(source, "grieflogger.command.page", 2)) 21 | .then(Commands.argument("page", IntegerArgumentType.integer()) 22 | .executes(context -> page(context.getSource(), IntegerArgumentType.getInteger(context, "page")))); 23 | } 24 | 25 | private static int page(CommandSourceStack source, int page) { 26 | if (source.getPlayer() instanceof GriefLoggerServerPlayer player) { 27 | List lookupResults = player.grieflogger$getPages(); 28 | if (page > 0 && page <= lookupResults.size()) { 29 | Page pageToDisplay = lookupResults.get(page - 1); 30 | pageToDisplay.sendToPlayer((ServerPlayer) player); 31 | } else { 32 | source.sendFailure(GriefLogger.translate("lookup.invalid_page", GriefLogger.getName())); 33 | } 34 | } 35 | return 1; 36 | } 37 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/IFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public interface IFilter { 11 | 12 | default char getPrefix() { 13 | return getName().charAt(0); 14 | } 15 | 16 | String getName(); 17 | 18 | List getOptions(); 19 | 20 | default String[] listSuggestions(SuggestionsBuilder builder, String prefix, String suffix) { 21 | if (suffix.contains(",")) { 22 | int lastIndexOf = suffix.lastIndexOf(","); 23 | String[] usedUsernames = suffix.substring(0, lastIndexOf).split(","); 24 | String suffixPrefix = suffix.substring(0, lastIndexOf); 25 | 26 | return getOptions().stream() 27 | .filter(s -> !Arrays.asList(usedUsernames).contains(s)) 28 | .map(s -> getName() + '.' + suffixPrefix + "," + s) 29 | .toArray(String[]::new); 30 | } 31 | 32 | return getOptions().stream() 33 | .map(s -> getName() + '.' + s) 34 | .toArray(String[]::new); 35 | } 36 | 37 | IFilter parse(StringReader reader, String suffix) throws CommandSyntaxException; 38 | 39 | default String[] listSuggestions(SuggestionsBuilder builder) { 40 | String str = builder.getRemaining(); 41 | String[] split = str.split("\\."); 42 | String prefix = split[0]; 43 | String suffix = split.length > 1 ? split[1] : ""; 44 | 45 | return listSuggestions(builder, prefix, suffix); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/ActionFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.action.Actions; 5 | import com.daqem.grieflogger.model.action.IAction; 6 | import com.mojang.brigadier.StringReader; 7 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class ActionFilter implements IFilter { 14 | 15 | private final List actions; 16 | 17 | public ActionFilter() { 18 | this(new ArrayList<>()); 19 | } 20 | 21 | public ActionFilter(List actions) { 22 | this.actions = actions; 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return "action"; 28 | } 29 | 30 | @Override 31 | public List getOptions() { 32 | return Arrays.asList(Actions.ACTIONS.stream() 33 | .map(IAction::name) 34 | .map(String::toLowerCase) 35 | .toArray(String[]::new)); 36 | } 37 | 38 | @Override 39 | public IFilter parse(StringReader reader, String suffix) throws CommandSyntaxException { 40 | String[] split = suffix.split(","); 41 | List actions = new ArrayList<>(Actions.getActions(split)); 42 | if (split.length != actions.size()) { 43 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader); 44 | } 45 | return new ActionFilter(actions); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "ActionFilter{" + 51 | "actions=" + actions + 52 | '}'; 53 | } 54 | 55 | public List getActions() { 56 | return actions; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/mixin/MixinBucketItem.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.mixin; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.event.block.BreakBlockEvent; 5 | import com.daqem.grieflogger.event.block.PlaceBlockEvent; 6 | import com.llamalad7.mixinextras.sugar.Local; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraft.world.InteractionHand; 10 | import net.minecraft.world.InteractionResult; 11 | import net.minecraft.world.entity.LivingEntity; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.item.BucketItem; 14 | import net.minecraft.world.item.ItemStack; 15 | import net.minecraft.world.level.Level; 16 | import net.minecraft.world.phys.BlockHitResult; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 21 | 22 | @Mixin(BucketItem.class) 23 | public class MixinBucketItem { 24 | 25 | @Inject( 26 | method = "use", 27 | at = @At( 28 | value = "INVOKE", 29 | target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/stats/Stat;)V", 30 | ordinal = 0 31 | ) 32 | ) 33 | private void onBucketFilled(Level level, Player player, InteractionHand interactionHand, CallbackInfoReturnable cir, @Local(ordinal = 1) ItemStack itemStack2, @Local BlockHitResult blockHitResult) { 34 | if (player instanceof ServerPlayer serverPlayer && itemStack2.getItem() instanceof BucketItem bucketItem) { 35 | BreakBlockEvent.breakBlock(level, blockHitResult.getBlockPos(), bucketItem.arch$getFluid().defaultFluidState().createLegacyBlock(), serverPlayer, null); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/TickEvents.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.config.GriefLoggerConfig; 5 | import com.daqem.grieflogger.database.Database; 6 | import com.daqem.grieflogger.thread.ThreadManager; 7 | import dev.architectury.event.events.common.TickEvent; 8 | 9 | import java.util.concurrent.ExecutionException; 10 | 11 | public class TickEvents { 12 | 13 | private static long lastTick = 0; 14 | 15 | public static void registerEvents() { 16 | TickEvent.SERVER_POST.register(server -> { 17 | ThreadManager.getAndRemoveCompleted().forEach( 18 | (future, onComplete) -> { 19 | try { 20 | onComplete.onComplete(future.get()); 21 | } catch (InterruptedException | ExecutionException e) { 22 | GriefLogger.LOGGER.error("Error executing task", e); 23 | } 24 | } 25 | ); 26 | 27 | if (lastTick % GriefLoggerConfig.queueFrequency.get() == 0) { 28 | Database database = GriefLogger.getDatabase(); 29 | if (database != null && (!database.queue.isEmpty() || !database.batchQueue.isEmpty())) { 30 | ThreadManager.execute(() -> { 31 | database.queue.execute(); 32 | database.batchQueue.execute(); 33 | }); 34 | } 35 | } 36 | 37 | if (lastTick % GriefLoggerConfig.helloFrequency.get() == 0) { 38 | ThreadManager.execute(() -> { 39 | // Send hello packet to server to keep connection alive 40 | Database database = GriefLogger.getDatabase(); 41 | if (database != null) { 42 | database.queue.hello(); 43 | } 44 | }); 45 | } 46 | 47 | lastTick++; 48 | }); 49 | } 50 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/UsernameRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class UsernameRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public UsernameRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS usernames (" + 20 | "id " + database.getDialect().getDataType("integer") + " PRIMARY KEY" + (database.getDialect() instanceof MySQLDialect ? " AUTO_INCREMENT" : "") + "," + 21 | "time " + database.getDialect().getDataType("bigint") + " NOT NULL," + 22 | "uuid " + database.getDialect().getDataType("varchar") + "(36) NOT NULL," + 23 | "name " + database.getDialect().getDataType("varchar") + "(16) NOT NULL," + 24 | "UNIQUE(uuid, name)" + 25 | ")"; 26 | if (database.getDialect() instanceof MySQLDialect) { 27 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 28 | } else { 29 | sql += ";"; 30 | } 31 | database.createTable(sql); 32 | } 33 | 34 | public void insert(long time, String uuid, String name) { 35 | String query = database.getDialect().getInsertIgnore() + " INTO usernames(time, uuid, name) VALUES(?, ?, ?);"; 36 | 37 | database.queue.add(connection -> { 38 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 39 | preparedStatement.setLong(1, time); 40 | preparedStatement.setString(2, uuid); 41 | preparedStatement.setString(3, name); 42 | preparedStatement.executeUpdate(); 43 | } 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/UserFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.database.cache.Caches; 5 | import com.mojang.brigadier.StringReader; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Collectors; 14 | 15 | public class UserFilter implements IFilter { 16 | 17 | private final Map usernames; 18 | 19 | public UserFilter() { 20 | this(new HashMap<>()); 21 | } 22 | 23 | public UserFilter(Map usernames) { 24 | this.usernames = usernames; 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return "user"; 30 | } 31 | 32 | @Override 33 | public List getOptions() { 34 | return Caches.USER.getAllUsernames().values().stream().toList(); 35 | } 36 | 37 | @Override 38 | public IFilter parse(StringReader reader, String suffix) throws CommandSyntaxException { 39 | String[] split = suffix.split(","); 40 | Map usernames = Caches.USER.getAllUsernames().entrySet().stream() 41 | .filter(entry -> Arrays.asList(split).contains(entry.getValue())) 42 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 43 | if (split.length != usernames.size()) { 44 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader); 45 | } 46 | return new UserFilter(usernames); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "UserFilter{" + 52 | "usernames=" + usernames + 53 | '}'; 54 | } 55 | 56 | public List getUserIds() { 57 | return usernames.keySet().stream().toList(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/RadiusFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.BlockPosition; 5 | import com.mojang.brigadier.StringReader; 6 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.IntStream; 12 | 13 | public class RadiusFilter implements IFilter { 14 | 15 | private final int radius; 16 | private BlockPosition position = new BlockPosition(0, 0, 0); 17 | 18 | public RadiusFilter() { 19 | this(0); 20 | } 21 | 22 | public RadiusFilter(int radius) { 23 | this.radius = radius; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return "radius"; 29 | } 30 | 31 | @Override 32 | public List getOptions() { 33 | return IntStream.rangeClosed(1, 100) 34 | .mapToObj(Integer::toString) 35 | .collect(Collectors.toList()); 36 | } 37 | 38 | @Override 39 | public IFilter parse(StringReader reader, String suffix) { 40 | return new RadiusFilter(Integer.parseInt(suffix)); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "RadiusFilter{" + 46 | "radius=" + radius + 47 | '}'; 48 | } 49 | 50 | public int getMinX() { 51 | return position.x() - radius; 52 | } 53 | 54 | public int getMaxX() { 55 | return position.x() + radius; 56 | } 57 | 58 | public int getMinY() { 59 | return position.y() - radius; 60 | } 61 | 62 | public int getMaxY() { 63 | return position.y() + radius; 64 | } 65 | 66 | public int getMinZ() { 67 | return position.z() - radius; 68 | } 69 | 70 | public int getMaxZ() { 71 | return position.z() + radius; 72 | } 73 | 74 | public void setPosition(BlockPosition position) { 75 | this.position = position; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [ "1.21.11" ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | publish: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | env: 16 | CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} 17 | MODRINTH_API_KEY: ${{ secrets.MODRINTH_API_KEY }} 18 | MAVEN_USER: ${{ secrets.MAVEN_USER }} 19 | MAVEN_PASS: ${{ secrets.MAVEN_PASS }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Set up JDK 21 26 | uses: actions/setup-java@v3 27 | with: 28 | java-version: '21' 29 | distribution: 'temurin' 30 | 31 | - name: Set up Python 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: '3.8' 35 | 36 | - name: Grant Permissions 37 | run: | 38 | chmod +x gradlew 39 | chmod +x relocate_natives/prepare.sh 40 | 41 | - name: Build Jars 42 | run: ./gradlew build 43 | 44 | - name: Retrieve Version 45 | id: gradle_version 46 | run: | 47 | echo "::set-output name=VERSION_NAME::$(./gradlew -q :common:printVersionName)" 48 | 49 | - name: Check if tag exists 50 | uses: mukunku/tag-exists-action@v1.5.0 51 | id: checkTag 52 | with: 53 | tag: ${{ steps.gradle_version.outputs.VERSION_NAME }} 54 | 55 | - name: Fail if tag exists 56 | if: steps.checkTag.outputs.exists == 'true' 57 | run: exit 1 58 | 59 | - name: Publish to DAQEM Maven 60 | run: ./gradlew publish 61 | 62 | - name: Publish to Modrinth 63 | run: ./gradlew modrinth 64 | 65 | - name: Publish to CurseForge 66 | run: ./gradlew curseforge 67 | 68 | - name: Publish to GitHub Releases 69 | uses: ncipollo/release-action@v1 70 | with: 71 | artifacts: | 72 | fabric/build/libs/*.jar 73 | neoforge/build/libs/*.jar 74 | name: ${{ steps.gradle_version.outputs.VERSION_NAME }} 75 | tag: ${{ steps.gradle_version.outputs.VERSION_NAME }} -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/mixin/MixinArmorStand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.mixin; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.action.BlockAction; 5 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 6 | import net.minecraft.resources.Identifier; 7 | import net.minecraft.world.InteractionHand; 8 | import net.minecraft.world.InteractionResult; 9 | import net.minecraft.world.entity.EntityType; 10 | import net.minecraft.world.entity.LivingEntity; 11 | import net.minecraft.world.entity.decoration.ArmorStand; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.level.Level; 14 | import net.minecraft.world.phys.Vec3; 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.CallbackInfoReturnable; 19 | 20 | @Mixin(ArmorStand.class) 21 | public abstract class MixinArmorStand extends LivingEntity { 22 | 23 | protected MixinArmorStand(EntityType entityType, Level level) { 24 | super(entityType, level); 25 | } 26 | 27 | @Inject(method = "interactAt", at = @At("RETURN")) 28 | private void onInteractAt(Player player, Vec3 vec3, InteractionHand interactionHand, CallbackInfoReturnable cir) { 29 | if ((cir.getReturnValue() == InteractionResult.SUCCESS || cir.getReturnValue() == InteractionResult.SUCCESS_SERVER) && player instanceof GriefLoggerServerPlayer glPlayer) { 30 | Identifier entityLocation = this.getType().arch$registryName(); 31 | if (entityLocation != null) { 32 | Services.BLOCK.insertEntity( 33 | glPlayer.grieflogger$asServerPlayer().getUUID(), 34 | this.level().dimension().identifier().toString(), 35 | this.blockPosition(), 36 | entityLocation.toString(), 37 | BlockAction.INTERACT_ENTITY 38 | ); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/ItemFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | import net.minecraft.core.registries.BuiltInRegistries; 6 | import net.minecraft.world.item.Item; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | public abstract class ItemFilter implements IFilter { 14 | 15 | private final List items; 16 | 17 | public ItemFilter() { 18 | this(new ArrayList<>()); 19 | } 20 | 21 | public ItemFilter(List items) { 22 | this.items = items; 23 | } 24 | 25 | @Override 26 | public List getOptions() { 27 | return new ArrayList<>(BuiltInRegistries.ITEM.stream().map(x -> x.arch$registryName().toString().replace("minecraft:", "")).toList()); 28 | } 29 | 30 | protected List getItemsFromSuffix(StringReader reader, String suffix) throws CommandSyntaxException { 31 | String[] split = Arrays.stream(suffix.split(",")).map(s -> s.replace("minecraft:", "").trim()).toArray(String[]::new); 32 | List items = BuiltInRegistries.ITEM.stream() 33 | .filter(item -> Arrays.asList(split).contains(item.arch$registryName().toString().replace("minecraft:", ""))) 34 | .toList(); 35 | if (split.length != items.size()) { 36 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader); 37 | } 38 | return items; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "ItemFilter{" + 44 | "items=" + items.stream().map(Item::arch$registryName).toList() + 45 | '}'; 46 | } 47 | 48 | public List getMaterials() { 49 | return items.stream() 50 | .map(Item::arch$registryName) 51 | .filter(Objects::nonNull) 52 | .map(Object::toString) 53 | .map(x -> x.replace("minecraft:", "")) 54 | .toList(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/Time.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.network.chat.Component; 6 | import net.minecraft.network.chat.HoverEvent; 7 | import net.minecraft.network.chat.MutableComponent; 8 | import net.minecraft.network.chat.Style; 9 | 10 | import java.util.Date; 11 | 12 | public record Time(long time) { 13 | 14 | private static final Component MINUTES = TimeUnit.MINUTE.getComponent(); 15 | private static final Component HOURS = TimeUnit.HOUR.getComponent(); 16 | private static final Component DAYS = TimeUnit.DAY.getComponent(); 17 | private static final Component YEARS = TimeUnit.YEAR.getComponent(); 18 | 19 | private static final long MINUTE = TimeUnit.MINUTE.getMilliseconds(); 20 | private static final long HOUR = TimeUnit.HOUR.getMilliseconds(); 21 | private static final long DAY = TimeUnit.DAY.getMilliseconds(); 22 | private static final long YEAR = TimeUnit.YEAR.getMilliseconds(); 23 | 24 | public MutableComponent getFormattedTimeAgo() { 25 | long timeAgo = System.currentTimeMillis() - time; 26 | if (timeAgo < HOUR / 2) { 27 | return getTimeAgoComponent((double) timeAgo / MINUTE, MINUTES); 28 | } else if (timeAgo < DAY / 2) { 29 | return getTimeAgoComponent((double) timeAgo / HOUR, HOURS); 30 | } else if (timeAgo < YEAR / 2) { 31 | return getTimeAgoComponent((double) timeAgo / DAY, DAYS); 32 | } else { 33 | return getTimeAgoComponent((double) timeAgo / YEAR, YEARS); 34 | } 35 | } 36 | 37 | private MutableComponent getTimeAgoComponent(double timeAgo, Component unit) { 38 | return GriefLogger.translate("lookup.time.ago", String.format("%.2f", timeAgo), GriefLogger.translate("time.divider"), unit) 39 | .withStyle(Style.EMPTY 40 | .withColor(ChatFormatting.GRAY) 41 | .withHoverEvent(new HoverEvent.ShowText(GriefLogger.literal(new Date(time).toString()) 42 | .withStyle(ChatFormatting.GRAY)))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/mixin/MixinItemStack.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.mixin; 2 | 3 | import com.daqem.grieflogger.event.item.BreakItemEvent; 4 | import net.minecraft.advancements.CriteriaTriggers; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.util.RandomSource; 8 | import net.minecraft.world.entity.LivingEntity; 9 | import net.minecraft.world.item.Item; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraft.world.item.enchantment.EnchantmentHelper; 12 | import org.jetbrains.annotations.Nullable; 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.CallbackInfo; 17 | 18 | import java.util.function.Consumer; 19 | 20 | @Mixin(ItemStack.class) 21 | public class MixinItemStack { 22 | 23 | @Inject(at = @At(value = "HEAD"), method = "hurtAndBreak(ILnet/minecraft/server/level/ServerLevel;Lnet/minecraft/server/level/ServerPlayer;Ljava/util/function/Consumer;)V") 24 | private void hurtAndBreak(int i, ServerLevel serverLevel, ServerPlayer serverPlayer, Consumer consumer, CallbackInfo ci) { 25 | ItemStack itemStack = (ItemStack) (Object) this; 26 | if (itemStack.isDamageableItem()) { 27 | if (serverPlayer == null || !serverPlayer.hasInfiniteMaterials()) { 28 | if (i > 0) { 29 | i = EnchantmentHelper.processDurabilityChange(serverLevel, itemStack, i); 30 | if (i <= 0) { 31 | return; 32 | } 33 | } 34 | 35 | if (serverPlayer != null && i != 0) { 36 | CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, itemStack, itemStack.getDamageValue() + i); 37 | } 38 | 39 | int j = itemStack.getDamageValue() + i; 40 | if (j >= itemStack.getMaxDamage()) { 41 | BreakItemEvent.breakItem(serverPlayer, itemStack.copyWithCount(1)); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/SessionService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.command.filter.ActionFilter; 4 | import com.daqem.grieflogger.command.filter.FilterList; 5 | import com.daqem.grieflogger.database.Database; 6 | import com.daqem.grieflogger.database.repository.SessionRepository; 7 | import com.daqem.grieflogger.model.action.SessionAction; 8 | import com.daqem.grieflogger.model.history.SessionHistory; 9 | import net.minecraft.core.BlockPos; 10 | import net.minecraft.world.level.Level; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.UUID; 15 | 16 | public class SessionService { 17 | 18 | private final SessionRepository sessionRepository; 19 | 20 | public SessionService(Database database) { 21 | this.sessionRepository = new SessionRepository(database); 22 | } 23 | 24 | public void createTable() { 25 | sessionRepository.createTable(); 26 | } 27 | 28 | public void createIndexes() { 29 | sessionRepository.createIndexes(); 30 | } 31 | 32 | public void insert(UUID userUuid, Level level, BlockPos pos, SessionAction sessionAction) { 33 | sessionRepository.insert( 34 | System.currentTimeMillis(), 35 | userUuid.toString(), 36 | level.dimension().identifier().toString(), 37 | pos.getX(), pos.getY(), pos.getZ(), 38 | sessionAction.getId() 39 | ); 40 | } 41 | 42 | public List getFilteredSessionHistory(Level level, FilterList filterList) { 43 | Optional actionFilter = filterList.getActionFilter(); 44 | if ((actionFilter.isPresent() && actionFilter.get().getActions().stream().noneMatch(action -> action instanceof SessionAction)) 45 | || 46 | (filterList.getIncludeFilter().isPresent()) 47 | || 48 | (filterList.getExcludeFilter().isPresent()) 49 | ) { 50 | return List.of(); 51 | } 52 | return sessionRepository.getFilteredSessionHistory( 53 | level.dimension().identifier().toString(), 54 | filterList 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/BreakBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.block.BlockHandler; 4 | import com.daqem.grieflogger.block.container.ContainerHandler; 5 | import com.daqem.grieflogger.event.AbstractEvent; 6 | import com.daqem.grieflogger.model.action.BlockAction; 7 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 8 | import dev.architectury.event.EventResult; 9 | import dev.architectury.utils.value.IntValue; 10 | import net.minecraft.core.BlockPos; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.level.Level; 13 | import net.minecraft.world.level.block.Block; 14 | import net.minecraft.world.level.block.DoorBlock; 15 | import net.minecraft.world.level.block.entity.BlockEntity; 16 | import net.minecraft.world.level.block.state.BlockState; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | // [ADD THESE IMPORTS] 20 | import net.minecraft.world.item.Items; 21 | import net.minecraft.world.level.block.Blocks; 22 | 23 | public class BreakBlockEvent extends AbstractEvent { 24 | 25 | public static EventResult breakBlock(Level level, BlockPos pos, BlockState state, ServerPlayer player, @Nullable IntValue xp) { 26 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 27 | if (serverPlayer.grieflogger$isInspecting()) { 28 | return interrupt(); 29 | } 30 | 31 | Block block = state.getBlock(); 32 | if (BlockHandler.isBlockIntractable(block)) { 33 | if (block instanceof DoorBlock) { 34 | RemoveDoorInteractionsEvent.removeDoorInteractions(level, pos, state); 35 | } else { 36 | RemoveBlockInteractionsEvent.removeBlockInteractions(level, pos); 37 | } 38 | } 39 | if (state.hasBlockEntity()) { 40 | BlockEntity blockEntity = level.getBlockEntity(pos); 41 | ContainerHandler.getContainer(blockEntity).ifPresent(container -> 42 | BreakContainerEvent.breakContainer(serverPlayer, level, pos, container)); 43 | } 44 | 45 | LogBlockEvent.logBlock(serverPlayer, level, state, pos, BlockAction.BREAK_BLOCK); 46 | } 47 | return pass(); 48 | } 49 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/grieflogger/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "grieflogger.name": "破壞紀錄器", 3 | "grieflogger.commands.inspect.enabled": "%s - 已啟用檢查。", 4 | "grieflogger.commands.inspect.disabled": "%s - 已停用檢查。", 5 | "grieflogger.action.prefix.remove": "-", 6 | "grieflogger.action.prefix.add": "+", 7 | "grieflogger.action.prefix.neutral": "-", 8 | "grieflogger.action.break_block.past": "破壞了", 9 | "grieflogger.action.place_block.past": "放置了", 10 | "grieflogger.action.interact_block.past": "點擊了", 11 | "grieflogger.action.kill_entity.past": "殺死了", 12 | "grieflogger.action.add_item.past": "加入了", 13 | "grieflogger.action.remove_item.past": "移除了", 14 | "grieflogger.action.drop_item.past": "丟棄了", 15 | "grieflogger.action.pickup_item.past": "撿起了", 16 | "grieflogger.action.craft_item.past": "合成了", 17 | "grieflogger.action.break_item.past": "弄壞了", 18 | "grieflogger.action.consume_item.past": "使用了", 19 | "grieflogger.action.throw_item.past": "投擲了", 20 | "grieflogger.action.shoot_item.past": "射出了", 21 | "grieflogger.action.add_item_ender.past": "加入了終界箱", 22 | "grieflogger.action.remove_item_ender.past": "從終界箱移除了", 23 | "grieflogger.action.join.past": "加入了", 24 | "grieflogger.action.quit.past": "退出了", 25 | "grieflogger.time.minutes": "分", 26 | "grieflogger.time.hours": "時", 27 | "grieflogger.time.days": "天", 28 | "grieflogger.time.years": "年", 29 | "grieflogger.time.divider": "/", 30 | "grieflogger.lookup.position": "(x%s/y%s/z%s)", 31 | "grieflogger.lookup.time.ago": "%s%s%s前", 32 | "grieflogger.lookup.no_history": "%s - 此位置沒有找到任何紀錄。", 33 | "grieflogger.lookup.block.history_entry": "%s %s %s %s %s。", 34 | "grieflogger.lookup.container.history_entry": "%s %s %s %s x%s %s。", 35 | "grieflogger.lookup.session.history_entry": "%s %s %s %s。", 36 | "grieflogger.lookup.history_title": "破壞紀錄查詢", 37 | "grieflogger.lookup.history_header": "----- %s ------", 38 | "grieflogger.lookup.page": "頁數", 39 | "grieflogger.lookup.pages": " %s/%s", 40 | "grieflogger.lookup.invalid_page": "%s - 無效的頁碼。", 41 | "grieflogger.lookup.invalid_filter": "%s - 無效的篩選器:%s。", 42 | "grieflogger.lookup.no_results": "%s - 找不到任何結果。", 43 | "grieflogger.filter.action": "動作", 44 | "grieflogger.filter.exclude": "排除", 45 | "grieflogger.filter.include": "包含", 46 | "grieflogger.filter.radius": "範圍", 47 | "grieflogger.filter.time": "時間", 48 | "grieflogger.filter.user": "使用者" 49 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/InspectContainerEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.event.AbstractEvent; 5 | import com.daqem.grieflogger.model.history.IHistory; 6 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 7 | import com.daqem.grieflogger.thread.ThreadManager; 8 | import dev.architectury.event.EventResult; 9 | import net.minecraft.core.BlockPos; 10 | import net.minecraft.world.level.Level; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class InspectContainerEvent extends AbstractEvent { 16 | 17 | public static EventResult inspectContainer(GriefLoggerServerPlayer serverPlayer, Level level, BlockPos pos) { 18 | ThreadManager.submit(() -> { 19 | List history = new ArrayList<>(); 20 | List containerHistory = Services.CONTAINER.getHistory( 21 | level, 22 | pos); 23 | List interactionHistory = Services.BLOCK.getInteractionHistory( 24 | level, 25 | pos 26 | ); 27 | history.addAll(containerHistory); 28 | history.addAll(interactionHistory); 29 | history.sort((a, b) -> Long.compare(b.getTime().time(), a.getTime().time())); 30 | return history; 31 | }, serverPlayer::grieflogger$sendInspectMessage); 32 | return interrupt(); 33 | } 34 | 35 | public static EventResult inspectContainers(GriefLoggerServerPlayer serverPlayer, Level level, BlockPos pos, BlockPos connectionPos) { 36 | ThreadManager.submit(() -> { 37 | List history = new ArrayList<>(); 38 | List containerHistory = Services.CONTAINER.getHistory( 39 | level, 40 | pos, 41 | connectionPos); 42 | List interactionHistory = Services.BLOCK.getInteractionHistory( 43 | level, 44 | List.of(pos, connectionPos) 45 | ); 46 | history.addAll(containerHistory); 47 | history.addAll(interactionHistory); 48 | history.sort((a, b) -> Long.compare(b.getTime().time(), a.getTime().time())); 49 | return history; 50 | }, serverPlayer::grieflogger$sendInspectMessage); 51 | return interrupt(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/ItemHistory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import java.util.UUID; 4 | 5 | import com.daqem.grieflogger.GriefLogger; 6 | import com.daqem.grieflogger.i18n.LanguageManager; 7 | import com.daqem.grieflogger.model.BlockPosition; 8 | import com.daqem.grieflogger.model.SimpleItemStack; 9 | import com.daqem.grieflogger.model.Time; 10 | import com.daqem.grieflogger.model.User; 11 | import com.daqem.grieflogger.model.action.IAction; 12 | import com.daqem.grieflogger.model.action.ItemAction; 13 | 14 | import net.minecraft.core.component.DataComponentPatch; 15 | import net.minecraft.network.chat.Component; 16 | import net.minecraft.network.chat.HoverEvent; 17 | import net.minecraft.network.chat.MutableComponent; 18 | import net.minecraft.resources.Identifier; 19 | 20 | public class ItemHistory extends History { 21 | 22 | protected final SimpleItemStack itemStack; 23 | 24 | public ItemHistory(long time, String name, String uuid, int x, int y, int z, String material, DataComponentPatch data, int amount, int action) { 25 | this(new Time(time), new User(name, UUID.fromString(uuid)), new BlockPosition(x, y, z), new SimpleItemStack(Identifier.parse(material), amount, data), ItemAction.fromId(action)); 26 | } 27 | 28 | public ItemHistory(Time time, User user, BlockPosition position, SimpleItemStack itemStack, IAction action) { 29 | super(time, user, position, action); 30 | this.itemStack = itemStack; 31 | } 32 | 33 | public SimpleItemStack getItemStack() { 34 | return itemStack; 35 | } 36 | 37 | @Override 38 | public Component getComponent() { 39 | return getTime().getFormattedTimeAgo().append(" ") 40 | .append(getAction().getPrefix()).append(" ") 41 | .append(getUser().getNameComponent()).append(" ") 42 | .append(getAction().getPastTense()).append(" ") 43 | .append(Component.literal(String.valueOf(getItemStack().getCount()))).append(" ") 44 | .append(getMaterialComponent()); 45 | } 46 | 47 | @Override 48 | public Component getMaterialComponent() { 49 | MutableComponent mutableComponent = GriefLogger.themedLiteral(LanguageManager.getString(this.itemStack.getItem().getDescriptionId())); 50 | return mutableComponent 51 | .withStyle(mutableComponent 52 | .getStyle() 53 | .withHoverEvent(new HoverEvent.ShowItem(itemStack.toItemStack().copyWithCount(1)))); 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/block/BlockHandler.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.block; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.world.level.block.*; 5 | import net.minecraft.world.level.block.state.BlockState; 6 | import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class BlockHandler { 12 | 13 | public static boolean isBlockIntractable(Block block) { 14 | if (block instanceof FenceGateBlock 15 | || block instanceof DispenserBlock 16 | || block instanceof NoteBlock 17 | || block instanceof AbstractChestBlock 18 | || block instanceof AbstractFurnaceBlock 19 | || block instanceof LeverBlock 20 | || block instanceof TrapDoorBlock 21 | || block instanceof DoorBlock 22 | || block instanceof BrewingStandBlock 23 | || block instanceof DiodeBlock 24 | || block instanceof HopperBlock 25 | || block instanceof DropperBlock 26 | || block instanceof ShulkerBoxBlock 27 | || block instanceof BarrelBlock 28 | || block instanceof GrindstoneBlock 29 | || block instanceof ButtonBlock 30 | || block instanceof LoomBlock 31 | || block instanceof CraftingTableBlock 32 | || block instanceof CartographyTableBlock 33 | || block instanceof EnchantingTableBlock 34 | || block instanceof SmithingTableBlock 35 | || block instanceof StonecutterBlock 36 | || block instanceof CrafterBlock 37 | || block instanceof VaultBlock 38 | || block instanceof DaylightDetectorBlock 39 | || block instanceof SignBlock 40 | || block instanceof LecternBlock 41 | || block instanceof BeaconBlock 42 | ) { 43 | return true; 44 | } 45 | return getIntractableBlocks().contains(block.arch$registryName().toString()); 46 | } 47 | 48 | public static List getIntractableBlocks() { 49 | //TODO Add config option to add blocks to this list 50 | return List.of(); 51 | } 52 | 53 | public static Optional getSecondDoorPosition(BlockPos pos, BlockState state) { 54 | if (state.getBlock() instanceof DoorBlock) { 55 | if (state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER) { 56 | return Optional.of(pos.above()); 57 | } 58 | else if (state.getValue(DoorBlock.HALF) == DoubleBlockHalf.UPPER) { 59 | return Optional.of(pos.below()); 60 | } 61 | } 62 | return Optional.empty(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/ItemService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.command.filter.ActionFilter; 4 | import com.daqem.grieflogger.command.filter.FilterList; 5 | import com.daqem.grieflogger.database.Database; 6 | import com.daqem.grieflogger.database.repository.ItemRepository; 7 | import com.daqem.grieflogger.model.SimpleItemStack; 8 | import com.daqem.grieflogger.model.action.IAction; 9 | import com.daqem.grieflogger.model.action.ItemAction; 10 | import com.daqem.grieflogger.model.history.ItemHistory; 11 | import net.minecraft.core.BlockPos; 12 | import net.minecraft.resources.Identifier; 13 | import net.minecraft.world.level.Level; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | import java.util.UUID; 19 | 20 | public class ItemService { 21 | 22 | private final ItemRepository itemRepository; 23 | 24 | public ItemService(Database database) { 25 | this.itemRepository = new ItemRepository(database); 26 | } 27 | 28 | public void createTable() { 29 | itemRepository.createTable(); 30 | } 31 | 32 | public void createIndexes() { 33 | itemRepository.createIndexes(); 34 | } 35 | 36 | public void insert(UUID userUuid, Level level, BlockPos pos, SimpleItemStack item, ItemAction itemAction) { 37 | Identifier itemLocation = item.getItem().arch$registryName(); 38 | if (itemLocation != null) { 39 | itemRepository.insert(System.currentTimeMillis(), 40 | userUuid.toString(), 41 | level, 42 | pos.getX(), 43 | pos.getY(), 44 | pos.getZ(), 45 | item, 46 | itemAction.getId()); 47 | } 48 | } 49 | 50 | public void insertMap(UUID userUuid, Level level, BlockPos pos, Map> itemsMap) { 51 | itemRepository.insertMap(System.currentTimeMillis(), 52 | userUuid.toString(), 53 | level, 54 | pos.getX(), 55 | pos.getY(), 56 | pos.getZ(), 57 | itemsMap); 58 | } 59 | 60 | public List getFilteredItemHistory(Level level, FilterList filterList) { 61 | Optional actionFilter = filterList.getActionFilter(); 62 | if (actionFilter.isPresent() && actionFilter.get().getActions().stream().noneMatch(ItemService::isValidItemAction)) { 63 | return List.of(); 64 | } 65 | return itemRepository.getFilteredItemHistory( 66 | level, 67 | filterList 68 | ); 69 | } 70 | 71 | private static boolean isValidItemAction(IAction action) { 72 | return action instanceof ItemAction && (!action.equals(ItemAction.ADD_ITEM) || !action.equals(ItemAction.REMOVE_ITEM)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/block/container/ContainerHandler.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.block.container; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import net.minecraft.world.MenuProvider; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraft.world.item.Items; 8 | import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; 9 | import net.minecraft.world.level.block.entity.BlockEntity; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.lang.reflect.Field; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Optional; 16 | 17 | public class ContainerHandler { 18 | 19 | public static boolean hasContainer(BlockEntity blockEntity) { 20 | return blockEntity instanceof BaseContainerBlockEntity; 21 | } 22 | 23 | public static Optional getContainer(BlockEntity blockEntity) { 24 | return hasContainer(blockEntity) ? Optional.of((BaseContainerBlockEntity) blockEntity) : Optional.empty(); 25 | } 26 | 27 | public static boolean hasContainer(MenuProvider menuProvider) { 28 | return menuProvider instanceof BaseContainerBlockEntity; 29 | } 30 | 31 | public static Optional getContainer(MenuProvider menuProvider) { 32 | return hasContainer(menuProvider) ? Optional.of((BaseContainerBlockEntity) menuProvider) : Optional.empty(); 33 | } 34 | 35 | public static Optional> getContainers(MenuProvider menuProvider) { 36 | // Get all properties of the menu provider that are instances of BaseContainerBlockEntity 37 | List containers = new ArrayList<>(); 38 | 39 | for (Field field : menuProvider.getClass().getDeclaredFields()) { 40 | if (BaseContainerBlockEntity.class.isAssignableFrom(field.getType())) { 41 | try { 42 | // Make the field accessible if it's not already 43 | field.setAccessible(true); 44 | containers.add((BaseContainerBlockEntity) field.get(menuProvider)); 45 | } catch (IllegalAccessException e) { 46 | GriefLogger.LOGGER.error("Failed to access field: {}", field.getName(), e); 47 | } 48 | } 49 | } 50 | 51 | return containers.isEmpty() ? Optional.empty() : Optional.of(containers); 52 | } 53 | 54 | public static List getContainerItems(BaseContainerBlockEntity containerBlockEntity) { 55 | List itemStacks = new ArrayList<>(); 56 | for (int i = 0; i < containerBlockEntity.getContainerSize(); i++) { 57 | itemStacks.add(containerBlockEntity.getItem(i)); 58 | } 59 | return itemStacks.stream() 60 | .filter(itemStack -> !itemStack.isEmpty() && itemStack.getItem() != Items.AIR) 61 | .map(SimpleItemStack::new).toList(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/filter/TimeFilter.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.filter; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.model.TimeUnit; 5 | import com.mojang.brigadier.StringReader; 6 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class TimeFilter implements IFilter { 12 | 13 | private static final List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); 14 | private final long time; 15 | 16 | public TimeFilter() { 17 | this(0, TimeUnit.MINUTE); 18 | } 19 | 20 | public TimeFilter(long time, TimeUnit timeUnit) { 21 | this.time = System.currentTimeMillis() - timeUnit.getMilliseconds() * time; 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return "time"; 27 | } 28 | 29 | @Override 30 | public List getOptions() { 31 | return new ArrayList<>(); 32 | } 33 | 34 | @Override 35 | public String[] listSuggestions(SuggestionsBuilder builder, String prefix, String suffix) { 36 | if (suffix.isEmpty()) { 37 | return numbers.stream().map(s -> getName() + '.' + s).toArray(String[]::new); 38 | } else { 39 | // Check if suffix ends with a time unit 40 | if (TimeUnit.getAbbreviations().stream().anyMatch(suffix::endsWith)) { 41 | //check if the suffix without the time unit is a number using Integer.parseInt 42 | String number = suffix.substring(0, suffix.length() - 1); 43 | try { 44 | Integer.parseInt(number); 45 | return new String[]{getName() + '.' + number + suffix.substring(suffix.length() - 1)}; 46 | } catch (NumberFormatException e) { 47 | return new String[]{}; 48 | } 49 | } 50 | 51 | // Check if suffix is a number using Integer.parseInt 52 | try { 53 | Integer.parseInt(suffix); 54 | //add all time units to the suggestion 55 | return TimeUnit.getAbbreviations().stream().map(s -> getName() + '.' + suffix + s).toArray(String[]::new); 56 | } catch (NumberFormatException e) { 57 | return new String[]{}; 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public IFilter parse(StringReader reader, String suffix) { 64 | TimeUnit timeUnit = TimeUnit.values()[TimeUnit.getAbbreviations().indexOf(suffix.substring(suffix.length() - 1))]; 65 | int time = Integer.parseInt(suffix.substring(0, suffix.length() - timeUnit.getComponent().getString().length())); 66 | return new TimeFilter(time, timeUnit); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "TimeFilter{" + 72 | "time=" + time + 73 | '}'; 74 | } 75 | 76 | public long getTime() { 77 | return time; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/ChatRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class ChatRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public ChatRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS chats (" + 20 | "time " + database.getDialect().getDataType("bigint") + " NOT NULL," + 21 | "user " + database.getDialect().getDataType("integer") + " NOT NULL," + 22 | "level " + database.getDialect().getDataType("integer") + " NOT NULL," + 23 | "x " + database.getDialect().getDataType("integer") + " NOT NULL," + 24 | "y " + database.getDialect().getDataType("integer") + " NOT NULL," + 25 | "z " + database.getDialect().getDataType("integer") + " NOT NULL," + 26 | "message " + database.getDialect().getDataType("varchar") + "(256) NOT NULL," + 27 | "FOREIGN KEY(user) REFERENCES users(id)," + 28 | "FOREIGN KEY(level) REFERENCES levels(id)" + 29 | ")"; 30 | if (database.getDialect() instanceof MySQLDialect) { 31 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 32 | } else { 33 | sql += ";"; 34 | } 35 | database.createTable(sql); 36 | } 37 | 38 | public void createIndexes() { 39 | String sql; 40 | if (database.getDialect() instanceof MySQLDialect) { 41 | sql = "ALTER TABLE chats ADD INDEX coordinates (x, y, z);"; 42 | } else { 43 | sql = "CREATE INDEX IF NOT EXISTS coordinates ON chats (x, y, z);"; 44 | } 45 | database.execute(sql, false); 46 | } 47 | 48 | public void insert(long time, String userUuid, String levelName, int x, int y, int z, String message) { 49 | String query = database.getDialect().getInsertIgnore() + " INTO chats(time, user, level, x, y, z, message) " + 50 | "VALUES(?, (" + 51 | "SELECT id FROM users WHERE uuid = ?" + 52 | "), (" + 53 | "SELECT id FROM levels WHERE name = ?" + 54 | "), ?, ?, ?, ?);"; 55 | 56 | database.queue.add(connection -> { 57 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 58 | preparedStatement.setLong(1, time); 59 | preparedStatement.setString(2, userUuid); 60 | preparedStatement.setString(3, levelName); 61 | preparedStatement.setInt(4, x); 62 | preparedStatement.setInt(5, y); 63 | preparedStatement.setInt(6, z); 64 | preparedStatement.setString(7, message); 65 | preparedStatement.executeUpdate(); 66 | } 67 | }); 68 | } 69 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/CommandRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | 6 | import com.daqem.grieflogger.GriefLogger; 7 | import com.daqem.grieflogger.database.Database; 8 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 9 | 10 | public class CommandRepository extends Repository { 11 | 12 | private final Database database; 13 | 14 | public CommandRepository(Database database) { 15 | this.database = database; 16 | } 17 | 18 | public void createTable() { 19 | String sql = "CREATE TABLE IF NOT EXISTS commands (" + 20 | "time " + database.getDialect().getDataType("bigint") + " NOT NULL," + 21 | "user " + database.getDialect().getDataType("integer") + " NOT NULL," + 22 | "level " + database.getDialect().getDataType("integer") + " NOT NULL," + 23 | "x " + database.getDialect().getDataType("integer") + " NOT NULL," + 24 | "y " + database.getDialect().getDataType("integer") + " NOT NULL," + 25 | "z " + database.getDialect().getDataType("integer") + " NOT NULL," + 26 | "command " + database.getDialect().getDataType("varchar") + "(256) NOT NULL," + 27 | "FOREIGN KEY(user) REFERENCES users(id)," + 28 | "FOREIGN KEY(level) REFERENCES levels(id)" + 29 | ")"; 30 | if (database.getDialect() instanceof MySQLDialect) { 31 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 32 | } else { 33 | sql += ";"; 34 | } 35 | database.createTable(sql); 36 | } 37 | 38 | public void createIndexes() { 39 | String sql; 40 | if (database.getDialect() instanceof MySQLDialect) { 41 | sql = "ALTER TABLE commands ADD INDEX coordinates (x, y, z);"; 42 | } else { 43 | sql = "CREATE INDEX IF NOT EXISTS coordinates ON commands (x, y, z);"; 44 | } 45 | database.execute(sql, false); 46 | } 47 | 48 | public void insert(long time, String userUuid, String levelName, int x, int y, int z, String command) { 49 | String query = database.getDialect().getInsertIgnore() + " INTO commands(time, user, level, x, y, z, command) " + 50 | "VALUES(?, (" + 51 | "SELECT id FROM users WHERE uuid = ?" + 52 | "), (" + 53 | "SELECT id FROM levels WHERE name = ?" + 54 | "), ?, ?, ?, ?);"; 55 | 56 | database.queue.add(connection -> { 57 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 58 | preparedStatement.setLong(1, time); 59 | preparedStatement.setString(2, userUuid); 60 | preparedStatement.setString(3, levelName); 61 | preparedStatement.setInt(4, x); 62 | preparedStatement.setInt(5, y); 63 | preparedStatement.setInt(6, z); 64 | preparedStatement.setString(7, command); 65 | preparedStatement.executeUpdate(); 66 | } 67 | }); 68 | } 69 | } -------------------------------------------------------------------------------- /neoforge/src/main/java/com/daqem/grieflogger/neoforge/GriefLoggerPermissionsImpl.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.neoforge; 2 | 3 | import net.minecraft.commands.CommandSourceStack; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import net.minecraft.server.permissions.Permission; 6 | import net.minecraft.server.permissions.PermissionLevel; 7 | import net.neoforged.neoforge.server.permission.PermissionAPI; 8 | import net.neoforged.neoforge.server.permission.nodes.PermissionNode; 9 | import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | public class GriefLoggerPermissionsImpl { 15 | 16 | // Cache dynamic nodes so we don't re-create them constantly, 17 | // though ideally, nodes should be registered during startup event. 18 | // For simple compatibility without heavy registration logic, checking string nodes 19 | // often requires the permission manager (LuckPerms) to handle unregistered nodes gracefully. 20 | private static final Map> NODES = new ConcurrentHashMap<>(); 21 | 22 | public static boolean check(CommandSourceStack source, String permissionNode, int fallbackLevel) { 23 | if (source.getEntity() instanceof ServerPlayer player) { 24 | // LuckPerms on NeoForge can usually intercept permission checks even if 25 | // the node isn't strictly registered in the PermissionAPI registry, 26 | // depending on how the PermissionHandler is implemented. 27 | // However, the "Correct" way is to use PermissionAPI.getPermission. 28 | 29 | // If you want strict node registration, you'd need a registry event. 30 | // For lightweight compat, we try to get the permission value. 31 | 32 | // Create a temporary node wrapper or lookup existing (Logic depends on if you want 33 | // to pre-register specific nodes or allow dynamic strings). 34 | 35 | PermissionNode node = NODES.computeIfAbsent(permissionNode, id -> 36 | new PermissionNode<>( 37 | "grieflogger", 38 | id.replace("grieflogger.", ""), 39 | PermissionTypes.BOOLEAN, 40 | (p, uuid, context) -> false 41 | ) 42 | ); 43 | 44 | // Note: Using PermissionAPI with unregistered nodes might warn or default to false 45 | // depending on the implementation installed (e.g. Default vs LuckPerms). 46 | // LuckPerms usually handles unregistered lookups fine. 47 | try { 48 | return PermissionAPI.getPermission(player, node); 49 | } catch (Exception e) { 50 | // Fallback to OP if PermissionAPI fails or node is unknown/unregistered context 51 | return source.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.byId(fallbackLevel))); 52 | } 53 | } 54 | 55 | // Console / Command Blocks 56 | return source.permissions().hasPermission(new Permission.HasCommandLevel(PermissionLevel.byId(fallbackLevel))); 57 | } 58 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/SimpleItemStack.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model; 2 | 3 | import io.netty.buffer.Unpooled; 4 | import net.minecraft.core.component.DataComponentPatch; 5 | import net.minecraft.core.registries.BuiltInRegistries; 6 | import net.minecraft.network.RegistryFriendlyByteBuf; 7 | import net.minecraft.resources.Identifier; 8 | import net.minecraft.world.item.Item; 9 | import net.minecraft.world.item.ItemStack; 10 | import net.minecraft.world.level.Level; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Objects; 14 | 15 | public class SimpleItemStack { 16 | 17 | private final Item item; 18 | private int count; 19 | private final DataComponentPatch tag; 20 | 21 | public SimpleItemStack(ItemStack itemStack) { 22 | this(itemStack.getItem(), itemStack.getCount(), itemStack.getComponentsPatch()); 23 | } 24 | 25 | public SimpleItemStack(Identifier itemLocation, int count, DataComponentPatch tag) { 26 | this.item = BuiltInRegistries.ITEM.getValue(itemLocation); 27 | this.count = count; 28 | this.tag = tag; 29 | } 30 | 31 | public SimpleItemStack(Item item, int count, DataComponentPatch tag) { 32 | this.item = item; 33 | this.count = count; 34 | this.tag = tag; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | //DOES NOT CHECK COUNT 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | SimpleItemStack that = (SimpleItemStack) o; 43 | return Objects.equals(item, that.item) && Objects.equals(tag, that.tag); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Objects.hash(item, count, tag); 49 | } 50 | 51 | public Item getItem() { 52 | return item; 53 | } 54 | 55 | public int getCount() { 56 | return count; 57 | } 58 | 59 | public DataComponentPatch getTag() { 60 | return tag; 61 | } 62 | 63 | public boolean hasTag() { 64 | return tag != null; 65 | } 66 | 67 | public boolean hasNoTag() { 68 | return tag == null; 69 | } 70 | 71 | public void setCount(int count) { 72 | this.count = count; 73 | } 74 | 75 | public void addCount(int count) { 76 | this.count += count; 77 | } 78 | 79 | public byte @Nullable [] getTagBytes(Level level) { 80 | if (tag == null) { 81 | return null; 82 | } 83 | RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess()); 84 | DataComponentPatch.STREAM_CODEC.encode(buf, tag); 85 | byte[] temp = new byte[buf.readableBytes()]; 86 | buf.readBytes(temp); 87 | return temp; 88 | } 89 | 90 | public ItemStack toItemStack() { 91 | ItemStack itemStack = new ItemStack(item, count); 92 | itemStack.applyComponents(tag); 93 | return itemStack; 94 | } 95 | 96 | public boolean isEmpty() { 97 | return item.equals(ItemStack.EMPTY.getItem()) || count == 0; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/model/history/BlockHistory.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.model.history; 2 | 3 | import java.util.UUID; 4 | 5 | import com.daqem.grieflogger.GriefLogger; 6 | import com.daqem.grieflogger.i18n.LanguageManager; 7 | import com.daqem.grieflogger.model.BlockPosition; 8 | import com.daqem.grieflogger.model.Time; 9 | import com.daqem.grieflogger.model.User; 10 | import com.daqem.grieflogger.model.action.BlockAction; 11 | 12 | import net.minecraft.core.Holder; 13 | import net.minecraft.core.registries.BuiltInRegistries; 14 | import net.minecraft.network.chat.Component; 15 | import net.minecraft.network.chat.HoverEvent; 16 | import net.minecraft.network.chat.MutableComponent; 17 | import net.minecraft.resources.Identifier; 18 | import net.minecraft.world.item.Item; 19 | import net.minecraft.world.item.Items; 20 | import net.minecraft.world.level.block.Block; 21 | 22 | public class BlockHistory extends History { 23 | 24 | 25 | private final String material; 26 | 27 | public BlockHistory(long time, String name, String uuid, int x, int y, int z, String material, int blockAction) { 28 | this(new Time(time), new User(name, UUID.fromString(uuid)), new BlockPosition(x, y, z), material, BlockAction.fromId(blockAction)); 29 | } 30 | 31 | public BlockHistory(Time time, User user, BlockPosition position, String material, BlockAction action) { 32 | super(time, user, position, action); 33 | this.material = material; 34 | } 35 | 36 | @Override 37 | public Component getComponent() { 38 | return getTime().getFormattedTimeAgo().append(" ") 39 | .append(getAction().getPrefix()).append(" ") 40 | .append(getUser().getNameComponent()).append(" ") 41 | .append(getAction().getPastTense()).append(" ") 42 | .append(getMaterialComponent()); 43 | } 44 | 45 | public Component getMaterialComponent() { 46 | Holder.Reference blockReference = BuiltInRegistries.BLOCK.get(Identifier.parse(material)).orElse(null); 47 | Item item = blockReference != null ? blockReference.value().asItem() : Items.AIR; 48 | MutableComponent mutableComponent; 49 | if (blockReference != null) { 50 | mutableComponent = GriefLogger.themedLiteral(LanguageManager.getString(blockReference.value().getDescriptionId())); 51 | } else { 52 | mutableComponent = GriefLogger.themedLiteral(this.material.replace("minecraft:", "")); 53 | } 54 | if (item != Items.AIR) { 55 | return mutableComponent 56 | .withStyle(mutableComponent 57 | .getStyle() 58 | .withHoverEvent( 59 | new HoverEvent.ShowItem( 60 | item.getDefaultInstance() 61 | ))); 62 | } else { 63 | return mutableComponent 64 | .withStyle(mutableComponent 65 | .getStyle() 66 | .withHoverEvent(new HoverEvent.ShowText( 67 | Component.literal(this.material) 68 | ))); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.ResultSet; 5 | import java.sql.SQLException; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import com.daqem.grieflogger.GriefLogger; 10 | import com.daqem.grieflogger.database.Database; 11 | import com.daqem.grieflogger.database.dialect.MySQLDialect; 12 | 13 | public class UserRepository extends Repository { 14 | 15 | private final Database database; 16 | 17 | public UserRepository(Database database) { 18 | this.database = database; 19 | } 20 | 21 | public void createTable() { 22 | String sql = "CREATE TABLE IF NOT EXISTS users (" + 23 | "id " + database.getDialect().getDataType("integer") + " PRIMARY KEY" + (database.getDialect() instanceof MySQLDialect ? " AUTO_INCREMENT" : "") + "," + 24 | "name " + database.getDialect().getDataType("text") + " NOT NULL," + 25 | "uuid " + database.getDialect().getDataType("text") + " DEFAULT NULL UNIQUE" + 26 | ")"; 27 | if (database.getDialect() instanceof MySQLDialect) { 28 | sql += " ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;"; 29 | } else { 30 | sql += ";"; 31 | } 32 | database.createTable(sql); 33 | } 34 | 35 | public void insertOrUpdateName(String name, String uuid) { 36 | String query = "INSERT INTO users(name, uuid) VALUES(?, ?) " + 37 | database.getDialect().getOnConflictUpdate("uuid", "name = ?"); 38 | 39 | database.queue.add(connection -> { 40 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 41 | preparedStatement.setString(1, name); 42 | preparedStatement.setString(2, uuid); 43 | preparedStatement.setString(3, name); 44 | preparedStatement.executeUpdate(); 45 | } 46 | }); 47 | } 48 | 49 | public void insertNonPlayer(String name) { 50 | String query = "INSERT INTO users(name) VALUES('%s') " + 51 | database.getDialect().getOnConflictDoNothing("name"); 52 | 53 | database.queue.add(connection -> { 54 | try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { 55 | preparedStatement.setString(1, name); 56 | preparedStatement.executeUpdate(); 57 | } 58 | }); 59 | } 60 | 61 | public Map getAllUsernames() { 62 | Map usernames = new HashMap<>(); 63 | String query = """ 64 | SELECT id, name FROM users 65 | """; 66 | 67 | try (PreparedStatement preparedStatement = database.prepareStatement(query)) { 68 | ResultSet resultSet = preparedStatement.executeQuery(); 69 | while (resultSet.next()) { 70 | usernames.put( 71 | resultSet.getInt(1), 72 | resultSet.getString(2) 73 | ); 74 | } 75 | } catch (SQLException exception) { 76 | GriefLogger.LOGGER.error("Failed to get all usernames from database", exception); 77 | } 78 | return usernames; 79 | } 80 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/config/GriefLoggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.config; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.yamlconfig.YamlConfigExpectPlatform; 5 | import com.daqem.yamlconfig.api.config.ConfigExtension; 6 | import com.daqem.yamlconfig.api.config.ConfigType; 7 | import com.daqem.yamlconfig.api.config.entry.IConfigEntry; 8 | import com.daqem.yamlconfig.impl.config.ConfigBuilder; 9 | 10 | import java.util.List; 11 | 12 | public class GriefLoggerConfig { 13 | 14 | public static void init() { 15 | } 16 | 17 | public static final IConfigEntry useMysql; 18 | public static final IConfigEntry mysqlHost; 19 | public static final IConfigEntry mysqlPort; 20 | public static final IConfigEntry mysqlDatabase; 21 | public static final IConfigEntry mysqlUsername; 22 | public static final IConfigEntry mysqlPassword; 23 | public static final IConfigEntry mysqlTimeout; 24 | public static final IConfigEntry useIndexes; 25 | 26 | public static final IConfigEntry maxPageSize; 27 | 28 | public static final IConfigEntry language; 29 | 30 | public static final IConfigEntry queueFrequency; 31 | public static final IConfigEntry helloFrequency; 32 | 33 | static { 34 | ConfigBuilder config = new ConfigBuilder( 35 | GriefLogger.MOD_ID, 36 | "grieflogger-server", 37 | ConfigExtension.YAML, 38 | ConfigType.SERVER, 39 | YamlConfigExpectPlatform.getConfigDirectory().resolve(GriefLogger.MOD_ID) 40 | ); 41 | 42 | config.push("database"); 43 | useMysql = config.defineBoolean("useMysql", false) 44 | .withComments("Whether to use MySQL or SQLite"); 45 | mysqlHost = config.defineString("mysqlHost", "localhost", 1, 255) 46 | .withComments("MySQL host"); 47 | mysqlPort = config.defineInteger("mysqlPort", 3306, 1, 65535) 48 | .withComments("MySQL port"); 49 | mysqlDatabase = config.defineString("mysqlDatabase", "database", 1, 255) 50 | .withComments("MySQL database"); 51 | mysqlUsername = config.defineString("mysqlUsername", "username", 1, 255) 52 | .withComments("MySQL username"); 53 | mysqlPassword = config.defineString("mysqlPassword", "password", 1, 255) 54 | .withComments("MySQL password"); 55 | mysqlTimeout = config.defineInteger("mysqlTimeout", 5000, 1, 60000) 56 | .withComments("MySQL timeout"); 57 | useIndexes = config.defineBoolean("useIndexes", true) 58 | .withComments("Whether to use indexes (improves inspect/lookup speed)"); 59 | config.pop(); 60 | 61 | config.push("general"); 62 | maxPageSize = config.defineInteger("maxPageSize", 10, 1, 100) 63 | .withComments("Maximum page size"); 64 | config.pop(); 65 | 66 | config.push("server"); 67 | language = config.defineString("language", "en_us", 1, 10, List.of("en_us", "nl_nl", "zh_tw")) 68 | .withComments("The language to use for translations (en_us, nl_nl, zh_tw)"); 69 | config.pop(); 70 | 71 | config.push("queue"); 72 | queueFrequency = config.defineInteger("queueFrequency", 20, 1, 100) 73 | .withComments("The frequency at which the database queue is executed (every 'x' ticks)"); 74 | config.pop(); 75 | 76 | config.push("hello"); 77 | helloFrequency = config.defineInteger("helloFrequency", 600, 1, 1000) 78 | .withComments("The frequency at which the hello packet is sent to the server (every 'x' ticks)"); 79 | config.pop(); 80 | 81 | config.build(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/block/container/ContainerTransactionManager.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.block.container; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class ContainerTransactionManager implements IContainerTransactionManager { 16 | 17 | private final BaseContainerBlockEntity blockEntity; 18 | 19 | private final List initialItems = new ArrayList<>(); 20 | private final List finalItems = new ArrayList<>(); 21 | 22 | public ContainerTransactionManager(BaseContainerBlockEntity blockEntity) { 23 | this.blockEntity = blockEntity; 24 | for (int i = 0; i < blockEntity.getContainerSize(); i++) { 25 | addItem(blockEntity.getItem(i), initialItems); 26 | } 27 | } 28 | 29 | public void finalize(ServerPlayer serverPlayer) { 30 | constructFinalItems(); 31 | List removedItems = getRemovedItems(); 32 | List addedItems = getAddedItems(); 33 | 34 | Services.CONTAINER.insertMap( 35 | serverPlayer.getUUID(), 36 | blockEntity.getLevel() != null ? blockEntity.getLevel() : serverPlayer.level(), 37 | blockEntity.getBlockPos(), 38 | Map.of( 39 | ItemAction.REMOVE_ITEM, removedItems, 40 | ItemAction.ADD_ITEM, addedItems 41 | ) 42 | ); 43 | } 44 | 45 | private void constructFinalItems() { 46 | for (int i = 0; i < blockEntity.getContainerSize(); i++) { 47 | addItem(blockEntity.getItem(i), finalItems); 48 | } 49 | } 50 | 51 | private List getRemovedItems() { 52 | return new ArrayList<>(getDifference(initialItems, finalItems)); 53 | } 54 | 55 | private List getDifference(List x, List y) { 56 | List difference = new ArrayList<>(); 57 | for (SimpleItemStack xItem : x) { 58 | y.stream().filter(xItem::equals).findFirst().ifPresentOrElse(yItem -> { 59 | if (yItem.getCount() < xItem.getCount()) { 60 | difference.add(new SimpleItemStack(xItem.getItem(), xItem.getCount() - yItem.getCount(), xItem.getTag())); 61 | } 62 | }, () -> difference.add(xItem)); 63 | } 64 | return difference; 65 | } 66 | 67 | private List getAddedItems() { 68 | return new ArrayList<>(getDifference(finalItems, initialItems)); 69 | } 70 | 71 | private void addItem(ItemStack itemStack, List itemStackList) { 72 | if (itemStack.getItem().equals(Items.AIR)) { 73 | return; 74 | } 75 | 76 | if (itemStack.getCount() == 0) { 77 | return; 78 | } 79 | 80 | for (SimpleItemStack simpleItemStack : itemStackList) { 81 | if (simpleItemStack.getItem() == itemStack.getItem()) { 82 | if (simpleItemStack.hasTag() && !itemStack.getComponentsPatch().isEmpty() && simpleItemStack.getTag().equals(itemStack.getComponentsPatch())) { 83 | simpleItemStack.addCount(itemStack.getCount()); 84 | return; 85 | } else if (simpleItemStack.hasNoTag() && itemStack.getComponentsPatch().isEmpty()) { 86 | simpleItemStack.addCount(itemStack.getCount()); 87 | return; 88 | } 89 | } 90 | } 91 | 92 | itemStackList.add(new SimpleItemStack(itemStack)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/grieflogger/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "grieflogger.name": "GriefLogger", 3 | "grieflogger.commands.inspect.enabled": "%s - Enabled inspection.", 4 | "grieflogger.commands.inspect.disabled": "%s - Disabled inspection.", 5 | "grieflogger.action.prefix.remove": "-", 6 | "grieflogger.action.prefix.add": "+", 7 | "grieflogger.action.prefix.neutral": "-", 8 | "grieflogger.action.break_block.past": "broke", 9 | "grieflogger.action.place_block.past": "placed", 10 | "grieflogger.action.interact_block.past": "clicked", 11 | "grieflogger.action.kill_entity.past": "killed", 12 | "grieflogger.action.interact_entity.past": "clicked", 13 | "grieflogger.action.add_item.past": "added", 14 | "grieflogger.action.remove_item.past": "removed", 15 | "grieflogger.action.drop_item.past": "dropped", 16 | "grieflogger.action.pickup_item.past": "picked up", 17 | "grieflogger.action.craft_item.past": "crafted", 18 | "grieflogger.action.break_item.past": "broke", 19 | "grieflogger.action.consume_item.past": "consumed", 20 | "grieflogger.action.throw_item.past": "threw", 21 | "grieflogger.action.shoot_item.past": "shot", 22 | "grieflogger.action.add_item_ender.past": "added to ender chest", 23 | "grieflogger.action.remove_item_ender.past": "removed from ender chest", 24 | "grieflogger.action.join.past": "joined", 25 | "grieflogger.action.quit.past": "quit", 26 | "grieflogger.time.minutes": "m", 27 | "grieflogger.time.hours": "h", 28 | "grieflogger.time.days": "d", 29 | "grieflogger.time.years": "y", 30 | "grieflogger.time.divider": "/", 31 | "grieflogger.lookup.position": "(x%s/y%s/z%s)", 32 | "grieflogger.lookup.time.ago": "%s%s%s ago", 33 | "grieflogger.lookup.no_history": "%s - No history found for this location.", 34 | "grieflogger.lookup.block.history_entry": "%s %s %s %s %s.", 35 | "grieflogger.lookup.container.history_entry": "%s %s %s %s x%s %s.", 36 | "grieflogger.lookup.session.history_entry": "%s %s %s %s.", 37 | "grieflogger.lookup.history_title": "GriefLogger Lookup", 38 | "grieflogger.lookup.history_header": "----- %s ------", 39 | "grieflogger.lookup.page": "Page", 40 | "grieflogger.lookup.pages": " %s/%s", 41 | "grieflogger.lookup.invalid_page": "%s - Invalid page number.", 42 | "grieflogger.lookup.invalid_filter": "%s - Invalid filter: %s.", 43 | "grieflogger.lookup.no_results": "%s - No results found.", 44 | "grieflogger.filter.action": "action", 45 | "grieflogger.filter.exclude": "exclude", 46 | "grieflogger.filter.include": "include", 47 | "grieflogger.filter.radius": "radius", 48 | "grieflogger.filter.time": "time", 49 | "grieflogger.filter.user": "user", 50 | "yamlconfig.grieflogger": "GriefLogger", 51 | "yamlconfig.grieflogger.grieflogger-server": "Server Configuration", 52 | "yamlconfig.grieflogger.grieflogger-server.general": "General", 53 | "yamlconfig.grieflogger.grieflogger-server.general.maxPageSize": "Max Page Size", 54 | "yamlconfig.grieflogger.grieflogger-server.server": "Server", 55 | "yamlconfig.grieflogger.grieflogger-server.server.serverSideOnlyMode": "Server Side Only Mode", 56 | "yamlconfig.grieflogger.grieflogger-server.database": "Database", 57 | "yamlconfig.grieflogger.grieflogger-server.database.useMysql": "Use MySQL", 58 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlHost": "MySQL Host", 59 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlPort": "MySQL Port", 60 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlDatabase": "MySQL Database", 61 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlUsername": "MySQL Username", 62 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlPassword": "MySQL Password", 63 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlTimeout": "MySQL Timeout", 64 | "yamlconfig.grieflogger.grieflogger-server.database.useIndexes": "Use Indexes", 65 | "yamlconfig.grieflogger.grieflogger-server.hello": "Hello", 66 | "yamlconfig.grieflogger.grieflogger-server.hello.helloFrequency": "Hello Frequency", 67 | "yamlconfig.grieflogger.grieflogger-server.queue": "Queue", 68 | "yamlconfig.grieflogger.grieflogger-server.queue.queueFrequency": "Queue Frequency" 69 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/grieflogger/lang/nl_nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "grieflogger.name": "GriefLogger", 3 | "grieflogger.commands.inspect.enabled": "%s - Inspectie ingeschakeld.", 4 | "grieflogger.commands.inspect.disabled": "%s - Inspectie uitgeschakeld.", 5 | "grieflogger.action.prefix.remove": "-", 6 | "grieflogger.action.prefix.add": "+", 7 | "grieflogger.action.prefix.neutral": "-", 8 | "grieflogger.action.break_block.past": "brak", 9 | "grieflogger.action.place_block.past": "plaatste", 10 | "grieflogger.action.interact_block.past": "klikte op", 11 | "grieflogger.action.kill_entity.past": "doodde", 12 | "grieflogger.action.add_item.past": "voegde toe", 13 | "grieflogger.action.remove_item.past": "verwijderde", 14 | "grieflogger.action.drop_item.past": "liet vallen", 15 | "grieflogger.action.pickup_item.past": "raapte op", 16 | "grieflogger.action.craft_item.past": "maakte", 17 | "grieflogger.action.break_item.past": "brak", 18 | "grieflogger.action.consume_item.past": "consumeerde", 19 | "grieflogger.action.throw_item.past": "gooide", 20 | "grieflogger.action.shoot_item.past": "schoot", 21 | "grieflogger.action.add_item_ender.past": "stopte in enderkist", 22 | "grieflogger.action.remove_item_ender.past": "haalde uit enderkist", 23 | "grieflogger.action.join.past": "verbond", 24 | "grieflogger.action.quit.past": "verliet", 25 | "grieflogger.time.minutes": "m", 26 | "grieflogger.time.hours": "u", 27 | "grieflogger.time.days": "d", 28 | "grieflogger.time.years": "j", 29 | "grieflogger.time.divider": "/", 30 | "grieflogger.lookup.position": "(x%s/y%s/z%s)", 31 | "grieflogger.lookup.time.ago": "%s%s%s geleden", 32 | "grieflogger.lookup.no_history": "%s - Geen geschiedenis gevonden voor deze locatie.", 33 | "grieflogger.lookup.block.history_entry": "%s %s %s %s %s.", 34 | "grieflogger.lookup.container.history_entry": "%s %s %s %s x%s %s.", 35 | "grieflogger.lookup.session.history_entry": "%s %s %s %s.", 36 | "grieflogger.lookup.history_title": "GriefLogger Inspectie", 37 | "grieflogger.lookup.history_header": "----- %s ------", 38 | "grieflogger.lookup.page": "Pagina", 39 | "grieflogger.lookup.pages": " %s/%s", 40 | "grieflogger.lookup.invalid_page": "%s - Ongeldig paginanummer.", 41 | "grieflogger.lookup.invalid_filter": "%s - Ongeldige filter: %s.", 42 | "grieflogger.lookup.no_results": "%s - Geen resultaten gevonden.", 43 | "grieflogger.filter.action": "actie", 44 | "grieflogger.filter.exclude": "uitsluiten", 45 | "grieflogger.filter.include": "insluiten", 46 | "grieflogger.filter.radius": "straal", 47 | "grieflogger.filter.time": "tijd", 48 | "grieflogger.filter.user": "gebruiker", 49 | "yamlconfig.grieflogger": "GriefLogger", 50 | "yamlconfig.grieflogger.grieflogger-server": "Serverconfiguratie", 51 | "yamlconfig.grieflogger.grieflogger-server.general": "Algemeen", 52 | "yamlconfig.grieflogger.grieflogger-server.general.maxPageSize": "Maximale Paginagrootte", 53 | "yamlconfig.grieflogger.grieflogger-server.server": "Server", 54 | "yamlconfig.grieflogger.grieflogger-server.server.serverSideOnlyMode": "Alleen Server-side Modus", 55 | "yamlconfig.grieflogger.grieflogger-server.database": "Database", 56 | "yamlconfig.grieflogger.grieflogger-server.database.useMysql": "Gebruik MySQL", 57 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlHost": "MySQL Host", 58 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlPort": "MySQL Poort", 59 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlDatabase": "MySQL Database", 60 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlUsername": "MySQL Gebruikersnaam", 61 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlPassword": "MySQL Wachtwoord", 62 | "yamlconfig.grieflogger.grieflogger-server.database.mysqlTimeout": "MySQL Time-out", 63 | "yamlconfig.grieflogger.grieflogger-server.database.useIndexes": "Gebruik Indexen", 64 | "yamlconfig.grieflogger.grieflogger-server.hello": "Hallo", 65 | "yamlconfig.grieflogger.grieflogger-server.hello.helloFrequency": "Hallo Frequentie", 66 | "yamlconfig.grieflogger.grieflogger-server.queue": "Wachtrij", 67 | "yamlconfig.grieflogger.grieflogger-server.queue.queueFrequency": "Wachtrij Frequentie" 68 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/ContainerService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.command.filter.ActionFilter; 4 | import com.daqem.grieflogger.command.filter.FilterList; 5 | import com.daqem.grieflogger.database.Database; 6 | import com.daqem.grieflogger.database.repository.ContainerRepository; 7 | import com.daqem.grieflogger.model.SimpleItemStack; 8 | import com.daqem.grieflogger.model.action.IAction; 9 | import com.daqem.grieflogger.model.action.ItemAction; 10 | import com.daqem.grieflogger.model.history.IHistory; 11 | import net.minecraft.core.BlockPos; 12 | import net.minecraft.resources.Identifier; 13 | import net.minecraft.world.level.Level; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | import java.util.UUID; 19 | 20 | public class ContainerService { 21 | 22 | private final ContainerRepository containerRepository; 23 | 24 | public ContainerService(Database database) { 25 | this.containerRepository = new ContainerRepository(database); 26 | } 27 | 28 | public void createTable() { 29 | containerRepository.createTable(); 30 | } 31 | 32 | public void createIndexes() { 33 | containerRepository.createIndexes(); 34 | } 35 | 36 | public void insert(UUID userUuid, Level level, BlockPos pos, SimpleItemStack item, ItemAction itemAction) { 37 | Identifier itemLocation = item.getItem().arch$registryName(); 38 | if (itemLocation != null) { 39 | containerRepository.insert(System.currentTimeMillis(), 40 | userUuid.toString(), 41 | level, 42 | pos.getX(), 43 | pos.getY(), 44 | pos.getZ(), 45 | item, 46 | itemAction.getId()); 47 | } 48 | } 49 | 50 | public void insertList(UUID userUuid, Level level, BlockPos pos, List items, ItemAction itemAction) { 51 | containerRepository.insertList(System.currentTimeMillis(), 52 | userUuid.toString(), 53 | level, 54 | pos.getX(), 55 | pos.getY(), 56 | pos.getZ(), 57 | items, 58 | itemAction.getId()); 59 | } 60 | 61 | public void insertMap(UUID userUuid, Level level, BlockPos pos, Map> itemsMap) { 62 | containerRepository.insertMap(System.currentTimeMillis(), 63 | userUuid.toString(), 64 | level, 65 | pos.getX(), 66 | pos.getY(), 67 | pos.getZ(), 68 | itemsMap); 69 | } 70 | 71 | public List getHistory(Level level, BlockPos pos) { 72 | return containerRepository.getHistory( 73 | level, 74 | pos.getX(), 75 | pos.getY(), 76 | pos.getZ() 77 | ); 78 | } 79 | 80 | public List getHistory(Level level, BlockPos pos, BlockPos connectionPos) { 81 | return containerRepository.getHistory( 82 | level, 83 | pos.getX(), 84 | pos.getY(), 85 | pos.getZ(), 86 | connectionPos.getX(), 87 | connectionPos.getY(), 88 | connectionPos.getZ() 89 | ); 90 | } 91 | 92 | public List getFilteredContainerHistory(Level level, FilterList filterList) { 93 | Optional actionFilter = filterList.getActionFilter(); 94 | if ((actionFilter.isPresent() && actionFilter.get().getActions().stream().noneMatch(ContainerService::isValidItemAction))) { 95 | return List.of(); 96 | } 97 | return containerRepository.getFilteredContainerHistory( 98 | level, 99 | filterList 100 | ); 101 | } 102 | 103 | private static boolean isValidItemAction(IAction action) { 104 | return action.equals(ItemAction.ADD_ITEM) || action.equals(ItemAction.REMOVE_ITEM); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /relocate_natives/download_codesign.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import requests 4 | import tarfile 5 | import zipfile 6 | from pathlib import Path 7 | 8 | 9 | def get_platform_specific_filename(): 10 | system = platform.system() 11 | machine = platform.machine() 12 | 13 | if system == "Darwin": 14 | if machine == "arm64": 15 | return "apple-codesign-*-aarch64-apple-darwin.tar.gz" 16 | else: 17 | return "apple-codesign-*-x86_64-apple-darwin.tar.gz" 18 | elif system == "Linux": 19 | if machine == "aarch64": 20 | return "apple-codesign-*-aarch64-unknown-linux-musl.tar.gz" 21 | else: 22 | return "apple-codesign-*-x86_64-unknown-linux-musl.tar.gz" 23 | elif system == "Windows": 24 | if machine.endswith("64"): 25 | return "apple-codesign-*-x86_64-pc-windows-msvc.zip" 26 | else: 27 | return "apple-codesign-*-i686-pc-windows-msvc.zip" 28 | else: 29 | raise Exception(f"Unsupported platform: {system} {machine}") 30 | 31 | 32 | def download_and_unpack(): 33 | dest_dir = Path("./apple-codesign") 34 | 35 | repo_url = "https://api.github.com/repos/indygreg/apple-platform-rs/releases/latest" 36 | dest_dir.mkdir(exist_ok=True) 37 | 38 | # Fetch the latest release info from GitHub 39 | print("Fetching latest release information...") 40 | response = requests.get(repo_url) 41 | response.raise_for_status() 42 | release_data = response.json() 43 | 44 | # Ensure release data has assets 45 | if "assets" not in release_data: 46 | raise Exception("Release data does not contain assets.") 47 | 48 | # Determine the correct asset 49 | platform_filename = get_platform_specific_filename() 50 | asset = next((asset for asset in release_data["assets"] if asset["name"].startswith("apple-codesign-") and asset["name"].endswith(platform_filename.split("*")[-1])), None) 51 | 52 | if not asset: 53 | raise Exception(f"No matching asset found for platform: {platform_filename}") 54 | 55 | # Download the archive 56 | print(f"Downloading {asset['name']}...") 57 | download_url = asset["browser_download_url"] 58 | archive_path = dest_dir / asset["name"] 59 | 60 | with requests.get(download_url, stream=True) as r: 61 | r.raise_for_status() 62 | with open(archive_path, "wb") as f: 63 | for chunk in r.iter_content(chunk_size=8192): 64 | f.write(chunk) 65 | 66 | print(f"Downloaded to {archive_path}") 67 | 68 | # Extract the archive 69 | print("Extracting archive...") 70 | temp_extract_dir = dest_dir / "temp_extract" 71 | temp_extract_dir.mkdir(parents=True, exist_ok=True) 72 | 73 | if archive_path.suffix == ".zip": 74 | with zipfile.ZipFile(archive_path, "r") as zip_ref: 75 | zip_ref.extractall(temp_extract_dir) 76 | elif archive_path.suffixes[-2:] == [".tar", ".gz"]: 77 | with tarfile.open(archive_path, "r:gz") as tar_ref: 78 | tar_ref.extractall(temp_extract_dir) 79 | else: 80 | raise Exception(f"Unknown archive format: {archive_path}") 81 | 82 | # Move contents of the root directory inside the archive to dest_dir 83 | root_dir = next(temp_extract_dir.iterdir()) # Assuming only one root directory 84 | for item in root_dir.iterdir(): 85 | target_path = dest_dir / item.name 86 | if target_path.exists(): 87 | if target_path.is_dir(): 88 | os.rmdir(target_path) 89 | else: 90 | os.remove(target_path) 91 | item.rename(target_path) 92 | 93 | # Clean up temporary directories 94 | for item in temp_extract_dir.iterdir(): 95 | if item.is_dir(): 96 | os.rmdir(item) 97 | temp_extract_dir.rmdir() 98 | 99 | print(f"Extracted to {dest_dir}") 100 | 101 | # Clean up the archive 102 | os.remove(archive_path) 103 | print(f"Removed archive {archive_path}") 104 | 105 | 106 | if __name__ == "__main__": 107 | try: 108 | download_and_unpack() 109 | except Exception as e: 110 | print(f"Error: {e}") 111 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/argument/FilterArgument.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.argument; 2 | 3 | import com.daqem.grieflogger.command.filter.*; 4 | import com.mojang.brigadier.StringReader; 5 | import com.mojang.brigadier.arguments.ArgumentType; 6 | import com.mojang.brigadier.context.CommandContext; 7 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 8 | import com.mojang.brigadier.suggestion.Suggestions; 9 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 10 | import net.minecraft.commands.SharedSuggestionProvider; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.concurrent.CompletableFuture; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.IntStream; 17 | 18 | public class FilterArgument implements ArgumentType { 19 | 20 | public static FilterArgument filter() { 21 | return new FilterArgument(); 22 | } 23 | 24 | @Override 25 | public IFilter parse(StringReader reader) throws CommandSyntaxException { 26 | int cursor = reader.getCursor(); 27 | while (reader.canRead() && isAllowedInFilter(reader.peek())) { 28 | reader.skip(); 29 | } 30 | 31 | int indexOfColon = reader.getString().indexOf('.', cursor); 32 | if (indexOfColon == -1) { 33 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, "Expected filter"); 34 | } 35 | 36 | String prefix = reader.getString().substring(cursor, indexOfColon); 37 | IFilter filter = Filters.fromPrefix(prefix); 38 | if (filter == null) { 39 | throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, "Invalid filter"); 40 | } 41 | 42 | String suffix = reader.getString().substring(indexOfColon + 1, reader.getCursor()); 43 | return filter.parse(reader, suffix); 44 | } 45 | 46 | public boolean isAllowedInFilter(char c) { 47 | return c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_' || c == ':' || c == '/' || c == '.' || c == '-' || c == ','; 48 | } 49 | 50 | @Override 51 | public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { 52 | return SharedSuggestionProvider.suggest(getSuggestions(context, builder), builder); 53 | } 54 | 55 | private String[] getSuggestions(CommandContext context, SuggestionsBuilder builder) { 56 | String prefix = builder.getRemaining().toLowerCase().split("\\.")[0]; 57 | String[] empty = new String[0]; 58 | List filters = getFilters(context); 59 | 60 | boolean hasItemFilter = filters.stream().anyMatch(x -> x instanceof ItemFilter); 61 | 62 | if (prefix.isEmpty()) { 63 | return Filters.getFilteredSuggestions(filters, hasItemFilter); 64 | } 65 | 66 | IFilter filter = Filters.fromPrefix(prefix); 67 | if (filter == null 68 | || filter instanceof ItemFilter && hasItemFilter 69 | || filters.stream().anyMatch(x -> x != null && x.getClass().equals(filter.getClass()))) { 70 | return empty; 71 | } 72 | 73 | return filter.listSuggestions(builder); 74 | } 75 | 76 | public static IFilter getFilter(CommandContext context, String name) throws CommandSyntaxException { 77 | var filter = context.getArgument(name, String.class); 78 | return new FilterArgument().parse(new StringReader(filter)); 79 | } 80 | 81 | private List getFilters(CommandContext context) { 82 | return IntStream.rangeClosed(1, 5) 83 | .mapToObj(i -> getOptionalFilter(context, "filter" + i)) 84 | .filter(Optional::isPresent) 85 | .map(Optional::get) 86 | .collect(Collectors.toList()); 87 | } 88 | 89 | private Optional getOptionalFilter(CommandContext context, String key) { 90 | try { 91 | return Optional.of(context.getArgument(key, IFilter.class)); 92 | } catch (IllegalArgumentException ignored) { 93 | return Optional.empty(); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/page/Page.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command.page; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.config.GriefLoggerConfig; 5 | import com.daqem.grieflogger.model.history.IHistory; 6 | import net.minecraft.ChatFormatting; 7 | import net.minecraft.network.chat.ClickEvent; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraft.network.chat.MutableComponent; 10 | import net.minecraft.network.chat.Style; 11 | import net.minecraft.server.level.ServerPlayer; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | public class Page { 18 | 19 | private final List history; 20 | private final int page; 21 | private final int maxPage; 22 | private final boolean singleLocation; 23 | 24 | public Page(List history, int page, int maxPage, boolean singleLocation) { 25 | this.history = history; 26 | this.page = page; 27 | this.maxPage = maxPage; 28 | this.singleLocation = singleLocation; 29 | } 30 | 31 | public List getHistory() { 32 | List components = new ArrayList<>(); 33 | 34 | components.add(getHeader()); 35 | 36 | for (IHistory history : this.history) { 37 | if (singleLocation) { 38 | components.add(history.getComponent()); 39 | } else { 40 | components.add(history.getComponentWithPos()); 41 | } 42 | } 43 | 44 | if (maxPage > 1) { 45 | components.add(getFooter()); 46 | } 47 | 48 | return components; 49 | } 50 | 51 | private Component getHeader() { 52 | MutableComponent header = GriefLogger.translate("lookup.history_header", GriefLogger.themedTranslate("lookup.history_title")); 53 | if (singleLocation && !history.isEmpty()) { 54 | header.append(" ").append(history.getFirst().getPosition().getComponent()); 55 | } 56 | return header; 57 | } 58 | 59 | private Component getFooter() { 60 | return Component.empty() 61 | .append(getArrowLeft() 62 | .append(" ") 63 | .append(GriefLogger.themedTranslate("lookup.page")) 64 | .append(" ")) 65 | .append(GriefLogger.translate("lookup.pages", page, maxPage).withStyle(getStyle(page + 1, page < maxPage).withColor(ChatFormatting.WHITE)) 66 | .append(" ") 67 | .append(getArrowRight())); 68 | } 69 | 70 | private MutableComponent getArrowLeft() { 71 | 72 | return GriefLogger.literal("⯇").withStyle(getStyle(page - 1, page > 1)); 73 | } 74 | 75 | private MutableComponent getArrowRight() { 76 | return GriefLogger.literal("⯈").withStyle(getStyle(page + 1, page < maxPage)); 77 | } 78 | 79 | private ClickEvent getClickEvent(int page) { 80 | return new ClickEvent.RunCommand("/grieflogger page " + page); 81 | } 82 | 83 | private Style getStyle(int page, boolean enabled) { 84 | Style style = Style.EMPTY.withClickEvent(getClickEvent(page)).withColor(ChatFormatting.WHITE); 85 | if (!enabled) { 86 | style = Style.EMPTY.withColor(ChatFormatting.GRAY); 87 | } 88 | return style; 89 | } 90 | 91 | public void sendToPlayer(ServerPlayer serverPlayer) { 92 | getHistory().forEach(serverPlayer::sendSystemMessage); 93 | } 94 | 95 | public static List convertToPages(List history, boolean singleLocation) { 96 | List pages = new ArrayList<>(); 97 | Integer pageSize = GriefLoggerConfig.maxPageSize.get(); 98 | int maxPage = (int) Math.ceil((double) history.size() / (double) pageSize); 99 | int page = 1; 100 | for (int i = 0; i < history.size(); i += pageSize) { 101 | int finalPage = page; 102 | pages.add(history.subList(i, Math.min(i + pageSize, history.size())) 103 | .stream() 104 | .collect(Collectors.collectingAndThen(Collectors.toList(), x -> new Page(x, finalPage, maxPage, singleLocation)))); 105 | page++; 106 | } 107 | return pages; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/block/container/ContainersTransactionManager.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.block.container; 2 | 3 | import com.daqem.grieflogger.database.service.Services; 4 | import com.daqem.grieflogger.model.SimpleItemStack; 5 | import com.daqem.grieflogger.model.action.ItemAction; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.item.Items; 9 | import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class ContainersTransactionManager implements IContainerTransactionManager { 17 | 18 | private final List blockEntities; 19 | 20 | private final Map> initialItems = new HashMap<>(); 21 | private final Map> finalItems = new HashMap<>(); 22 | 23 | public ContainersTransactionManager(List blockEntities) { 24 | this.blockEntities = blockEntities; 25 | for (BaseContainerBlockEntity blockEntity : blockEntities) { 26 | initialItems.put(blockEntity, new ArrayList<>()); 27 | finalItems.put(blockEntity, new ArrayList<>()); 28 | for (int i = 0; i < blockEntity.getContainerSize(); i++) { 29 | addItem(blockEntity.getItem(i), initialItems.get(blockEntity)); 30 | } 31 | } 32 | } 33 | 34 | public void finalize(ServerPlayer serverPlayer) { 35 | for (BaseContainerBlockEntity blockEntity : blockEntities) { 36 | constructFinalItems(blockEntity); 37 | 38 | List removedItems = getRemovedItems(blockEntity); 39 | List addedItems = getAddedItems(blockEntity); 40 | 41 | Services.CONTAINER.insertMap( 42 | serverPlayer.getUUID(), 43 | blockEntity.getLevel() != null ? blockEntity.getLevel() : serverPlayer.level(), 44 | blockEntity.getBlockPos(), 45 | Map.of( 46 | ItemAction.REMOVE_ITEM, removedItems, 47 | ItemAction.ADD_ITEM, addedItems 48 | ) 49 | ); 50 | } 51 | 52 | } 53 | 54 | private void constructFinalItems(BaseContainerBlockEntity blockEntity) { 55 | for (int i = 0; i < blockEntity.getContainerSize(); i++) { 56 | addItem(blockEntity.getItem(i), finalItems.get(blockEntity)); 57 | } 58 | } 59 | 60 | private List getRemovedItems(BaseContainerBlockEntity blockEntity) { 61 | return new ArrayList<>(getDifference(initialItems.get(blockEntity), finalItems.get(blockEntity))); 62 | } 63 | 64 | private List getDifference(List x, List y) { 65 | List difference = new ArrayList<>(); 66 | for (SimpleItemStack xItem : x) { 67 | y.stream().filter(xItem::equals).findFirst().ifPresentOrElse(yItem -> { 68 | if (yItem.getCount() < xItem.getCount()) { 69 | difference.add(new SimpleItemStack(xItem.getItem(), xItem.getCount() - yItem.getCount(), xItem.getTag())); 70 | } 71 | }, () -> difference.add(xItem)); 72 | } 73 | return difference; 74 | } 75 | 76 | private List getAddedItems(BaseContainerBlockEntity blockEntity) { 77 | return new ArrayList<>(getDifference(finalItems.get(blockEntity), initialItems.get(blockEntity))); 78 | } 79 | 80 | private void addItem(ItemStack itemStack, List itemStackList) { 81 | if (itemStack.getItem().equals(Items.AIR)) { 82 | return; 83 | } 84 | 85 | if (itemStack.getCount() == 0) { 86 | return; 87 | } 88 | 89 | for (SimpleItemStack simpleItemStack : itemStackList) { 90 | if (simpleItemStack.getItem() == itemStack.getItem()) { 91 | if (simpleItemStack.hasTag() && !itemStack.getComponentsPatch().isEmpty() && simpleItemStack.getTag().equals(itemStack.getComponentsPatch())) { 92 | simpleItemStack.addCount(itemStack.getCount()); 93 | return; 94 | } else if (simpleItemStack.hasNoTag() && itemStack.getComponentsPatch().isEmpty()) { 95 | simpleItemStack.addCount(itemStack.getCount()); 96 | return; 97 | } 98 | } 99 | } 100 | 101 | itemStackList.add(new SimpleItemStack(itemStack)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/command/LookupCommand.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.command; 2 | 3 | import com.daqem.grieflogger.GriefLogger; 4 | import com.daqem.grieflogger.GriefLoggerPermissions; 5 | import com.daqem.grieflogger.command.argument.FilterArgument; 6 | import com.daqem.grieflogger.command.filter.FilterList; 7 | import com.daqem.grieflogger.command.filter.IFilter; 8 | import com.daqem.grieflogger.command.page.Page; 9 | import com.daqem.grieflogger.database.service.Services; 10 | import com.daqem.grieflogger.model.history.IHistory; 11 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 12 | import com.daqem.grieflogger.thread.ThreadManager; 13 | import com.mojang.brigadier.StringReader; 14 | import com.mojang.brigadier.arguments.StringArgumentType; 15 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 16 | import com.mojang.brigadier.context.CommandContext; 17 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 18 | import com.mojang.brigadier.suggestion.Suggestions; 19 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 20 | import net.minecraft.commands.CommandSourceStack; 21 | import net.minecraft.commands.Commands; 22 | import net.minecraft.server.level.ServerPlayer; 23 | import net.minecraft.world.level.Level; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.concurrent.CompletableFuture; 28 | import java.util.stream.Collectors; 29 | 30 | public class LookupCommand implements ICommand { 31 | 32 | @Override 33 | public LiteralArgumentBuilder getCommand() { 34 | return Commands.literal("lookup") 35 | .requires(source -> GriefLoggerPermissions.check(source, "grieflogger.command.lookup", 2)) 36 | .then(Commands.argument("filters", StringArgumentType.greedyString()) 37 | .suggests(LookupCommand::suggestFilters) 38 | .executes(context -> lookup(context.getSource(), StringArgumentType.getString(context, "filters")))) 39 | .executes(context -> lookup(context.getSource(), "")); 40 | } 41 | 42 | private static int lookup(CommandSourceStack source, String filtersInput) { 43 | List filters = new ArrayList<>(); 44 | 45 | if (!filtersInput.isBlank()) { 46 | String[] parts = filtersInput.split("\\s+"); 47 | FilterArgument parser = new FilterArgument(); 48 | 49 | for (String part : parts) { 50 | try { 51 | IFilter filter = parser.parse(new StringReader(part)); 52 | filters.add(filter); 53 | } catch (CommandSyntaxException e) { 54 | source.sendFailure(GriefLogger.translate("lookup.invalid_filter", GriefLogger.getName(), part)); 55 | return 0; 56 | } 57 | } 58 | } 59 | 60 | return lookup(source, new FilterList(filters, source)); 61 | } 62 | 63 | private static int lookup(CommandSourceStack source, FilterList filterList) { 64 | if (source.getPlayer() instanceof GriefLoggerServerPlayer player) { 65 | ThreadManager.submit(() -> getHistory(source.getLevel(), filterList), filteredHistory -> { 66 | if (filteredHistory.isEmpty()) { 67 | source.sendFailure(GriefLogger.translate("lookup.no_results", GriefLogger.getName())); 68 | return; 69 | } 70 | List pages = Page.convertToPages(filteredHistory, false); 71 | player.grieflogger$setPages(pages); 72 | Page pageToDisplay = pages.getFirst(); 73 | pageToDisplay.sendToPlayer((ServerPlayer) player); 74 | }); 75 | } 76 | return 1; 77 | } 78 | 79 | private static CompletableFuture suggestFilters(CommandContext context, SuggestionsBuilder builder) { 80 | String remaining = builder.getRemaining(); 81 | int lastSpace = remaining.lastIndexOf(' '); 82 | int start = builder.getStart() + lastSpace + 1; 83 | SuggestionsBuilder offsetBuilder = builder.createOffset(start); 84 | return new FilterArgument().listSuggestions(context, offsetBuilder); 85 | } 86 | 87 | private static List getHistory(Level level, FilterList filterList) { 88 | List history = new ArrayList<>(); 89 | history.addAll(Services.BLOCK.getFilteredBlockHistory(level, filterList)); 90 | history.addAll(Services.SESSION.getFilteredSessionHistory(level, filterList)); 91 | history.addAll(Services.CONTAINER.getFilteredContainerHistory(level, filterList)); 92 | history.addAll(Services.ITEM.getFilteredItemHistory(level, filterList)); 93 | return history.stream() 94 | .sorted((x, y) -> Long.compare(y.getTime().time(), x.getTime().time())) 95 | .collect(Collectors.toList()); 96 | } 97 | } -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/event/block/RightClickBlockEvent.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.event.block; 2 | 3 | import com.daqem.grieflogger.block.BlockHandler; 4 | import com.daqem.grieflogger.block.container.ContainerHandler; 5 | import com.daqem.grieflogger.event.AbstractEvent; 6 | import com.daqem.grieflogger.model.action.BlockAction; 7 | import com.daqem.grieflogger.player.GriefLoggerServerPlayer; 8 | import dev.architectury.event.EventResult; 9 | import net.minecraft.core.BlockPos; 10 | import net.minecraft.core.Direction; 11 | import net.minecraft.world.InteractionHand; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.level.Level; 14 | import net.minecraft.world.level.block.Block; 15 | import net.minecraft.world.level.block.ChestBlock; 16 | import net.minecraft.world.level.block.DoorBlock; 17 | import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; 18 | import net.minecraft.world.level.block.entity.BlockEntity; 19 | import net.minecraft.world.level.block.state.BlockState; 20 | import net.minecraft.world.level.block.state.properties.ChestType; 21 | 22 | import java.util.Optional; 23 | 24 | public class RightClickBlockEvent extends AbstractEvent { 25 | 26 | public static EventResult rightClickBlock(Player player, InteractionHand hand, BlockPos pos, Direction direction) { 27 | if (player instanceof GriefLoggerServerPlayer serverPlayer) { 28 | if (hand == InteractionHand.MAIN_HAND) { 29 | 30 | Level level = player.level(); 31 | BlockState state = level.getBlockState(pos); 32 | Block block = state.getBlock(); 33 | 34 | if (serverPlayer.grieflogger$isInspecting()) { 35 | if (state.getBlock() instanceof DoorBlock) { 36 | return InspectDoorEvent.inspectDoor(serverPlayer, level, pos, state, true); 37 | } 38 | if (state.hasBlockEntity()) { 39 | BlockEntity blockEntity = level.getBlockEntity(pos); 40 | Optional container = ContainerHandler.getContainer(blockEntity); 41 | if (container.isPresent()) { 42 | if (state.hasProperty(ChestBlock.TYPE)) { 43 | ChestType chestType = state.getValue(ChestBlock.TYPE); 44 | if (chestType != ChestType.SINGLE) { 45 | Direction connectionDirection = state.getValue(ChestBlock.FACING); 46 | BlockPos connectionPos = pos; 47 | if (chestType == ChestType.LEFT) { 48 | if (connectionDirection == Direction.NORTH) { 49 | connectionPos = pos.east(); 50 | } else if (connectionDirection == Direction.SOUTH) { 51 | connectionPos = pos.west(); 52 | } else if (connectionDirection == Direction.WEST) { 53 | connectionPos = pos.north(); 54 | } else { 55 | connectionPos = pos.south(); 56 | } 57 | } 58 | if (chestType == ChestType.RIGHT) { 59 | if (connectionDirection == Direction.NORTH) { 60 | connectionPos = pos.west(); 61 | } else if (connectionDirection == Direction.SOUTH) { 62 | connectionPos = pos.east(); 63 | } else if (connectionDirection == Direction.WEST) { 64 | connectionPos = pos.south(); 65 | } else { 66 | connectionPos = pos.north(); 67 | } 68 | } 69 | return InspectContainerEvent.inspectContainers(serverPlayer, level, pos, connectionPos); 70 | } 71 | } 72 | return InspectContainerEvent.inspectContainer(serverPlayer, level, pos); 73 | } 74 | } 75 | return InspectBlockEvent.inspectBlock(serverPlayer, pos.relative(direction)); 76 | } 77 | 78 | if (BlockHandler.isBlockIntractable(block)) { 79 | LogBlockEvent.logBlock(serverPlayer, level, state, pos, BlockAction.INTERACT_BLOCK); 80 | } 81 | } 82 | } 83 | return pass(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /common/src/main/java/com/daqem/grieflogger/database/service/BlockService.java: -------------------------------------------------------------------------------- 1 | package com.daqem.grieflogger.database.service; 2 | 3 | import com.daqem.grieflogger.command.filter.ActionFilter; 4 | import com.daqem.grieflogger.command.filter.FilterList; 5 | import com.daqem.grieflogger.database.Database; 6 | import com.daqem.grieflogger.database.repository.BlockRepository; 7 | import com.daqem.grieflogger.model.action.BlockAction; 8 | import com.daqem.grieflogger.model.history.IHistory; 9 | import com.daqem.grieflogger.thread.OnComplete; 10 | import com.daqem.grieflogger.thread.ThreadManager; 11 | import net.minecraft.core.BlockPos; 12 | import net.minecraft.world.level.Level; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Optional; 17 | import java.util.UUID; 18 | 19 | public class BlockService { 20 | 21 | private final BlockRepository blockRepository; 22 | 23 | public BlockService(Database database) { 24 | this.blockRepository = new BlockRepository(database); 25 | } 26 | 27 | public void createTable() { 28 | blockRepository.createTable(); 29 | } 30 | 31 | public void createIndexes() { 32 | blockRepository.createIndexes(); 33 | } 34 | 35 | public void insertMaterial(UUID userUuid, String levelName, BlockPos pos, String material, BlockAction blockAction) { 36 | material = material.replace("minecraft:", ""); 37 | blockRepository.insertMaterial(System.currentTimeMillis(), userUuid.toString(), levelName, pos.getX(), pos.getY(), pos.getZ(), material, blockAction.getId()); 38 | } 39 | 40 | public void insertEntity(UUID userUuid, String levelName, BlockPos pos, String entity, BlockAction blockAction) { 41 | entity = entity.replace("minecraft:", ""); 42 | blockRepository.insertEntity(System.currentTimeMillis(), userUuid.toString(), levelName, pos.getX(), pos.getY(), pos.getZ(), entity, blockAction.getId()); 43 | } 44 | 45 | public List getBlockHistory(Level level, BlockPos pos) { 46 | return blockRepository.getBlockHistory( 47 | level.dimension().identifier().toString(), 48 | pos.getX(), 49 | pos.getY(), 50 | pos.getZ() 51 | ); 52 | } 53 | 54 | public void getBlockHistoryAsync(Level level, BlockPos pos, OnComplete> onComplete) { 55 | ThreadManager.submit(() -> getBlockHistory(level, pos), onComplete); 56 | } 57 | 58 | public List getBlockHistory(Level level, List pos) { 59 | List blockHistories = new ArrayList<>(); 60 | 61 | for (BlockPos blockPos : pos) { 62 | blockHistories.addAll(getBlockHistory(level, blockPos)); 63 | } 64 | 65 | return blockHistories.stream() 66 | .sorted((o1, o2) -> (int) (o2.getTime().time() - o1.getTime().time())) 67 | .toList(); 68 | } 69 | 70 | public void getBlockHistoryAsync(Level level, List pos, OnComplete> onComplete) { 71 | ThreadManager.submit(() -> getBlockHistory(level, pos), onComplete); 72 | } 73 | 74 | public List getInteractionHistory(Level level, BlockPos pos) { 75 | return blockRepository.getInteractionHistory( 76 | level.dimension().identifier().toString(), 77 | pos.getX(), 78 | pos.getY(), 79 | pos.getZ() 80 | ); 81 | } 82 | 83 | public List getInteractionHistory(Level level, List pos) { 84 | List blockHistories = new ArrayList<>(); 85 | 86 | for (BlockPos blockPos : pos) { 87 | blockHistories.addAll(getInteractionHistory(level, blockPos)); 88 | } 89 | 90 | return blockHistories.stream() 91 | .sorted((o1, o2) -> (int) (o2.getTime().time() - o1.getTime().time())) 92 | .toList(); 93 | } 94 | 95 | public void getInteractionHistoryAsync(Level level, List pos, OnComplete> onComplete) { 96 | ThreadManager.submit(() -> getInteractionHistory(level, pos), onComplete); 97 | } 98 | 99 | public void removeInteractionsForPosition(Level level, BlockPos secondPos) { 100 | blockRepository.removeInteractionsForPosition( 101 | level.dimension().identifier().toString(), 102 | secondPos.getX(), 103 | secondPos.getY(), 104 | secondPos.getZ() 105 | ); 106 | } 107 | 108 | public List getFilteredBlockHistory(Level level, FilterList filterList) { 109 | Optional actionFilter = filterList.getActionFilter(); 110 | if (actionFilter.isPresent() && actionFilter.get().getActions().stream().noneMatch(action -> action instanceof BlockAction)) { 111 | return List.of(); 112 | } 113 | return blockRepository.getFilteredBlockHistory( 114 | level.dimension().identifier().toString(), 115 | filterList 116 | ); 117 | } 118 | } 119 | --------------------------------------------------------------------------------