commit
6c112140a9
61 files changed
+2355
No files matched your search
@@ -0,0 +1,39 @@
|
||||
package ciomek.loon;
|
||||
|
||||
import ciomek.loon.commands.ChangePasswordCommand;
|
||||
import ciomek.loon.commands.RegisterCommand;
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.minecraft.core.net.command.CommandManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import turniplabs.halplibe.HalpLibe;
|
||||
import turniplabs.halplibe.event.defs.CommonEvents;
|
||||
import turniplabs.halplibe.util.dependency.Key;
|
||||
|
||||
public class Loon implements ModInitializer {
|
||||
public static final String MOD_ID = HalpLibe.registerMod("loon", true);
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
public static final String EnvVarPrefix = "LOON__";
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommonEvents.BEFORE_GAME_START.listen(Key.of(MOD_ID), this::beforeGameStart);
|
||||
CommonEvents.AFTER_GAME_START.listen(Key.of(MOD_ID), this::afterGameStart);
|
||||
LOGGER.info("Loon initialized.");
|
||||
|
||||
|
||||
CommandManager.registerCommand(new RegisterCommand());
|
||||
CommandManager.registerCommand(new ChangePasswordCommand());
|
||||
|
||||
MQTTClient.getInstance();
|
||||
}
|
||||
|
||||
public void beforeGameStart() {
|
||||
|
||||
}
|
||||
|
||||
public void afterGameStart() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ciomek.loon;
|
||||
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
public class TPSTracker {
|
||||
@Unique
|
||||
private static volatile double tps = 0;
|
||||
|
||||
public static double getTPS()
|
||||
{
|
||||
return tps;
|
||||
}
|
||||
|
||||
public static void setTPS(double tps)
|
||||
{
|
||||
TPSTracker.tps = tps;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package ciomek.loon.commands;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.ChangePasswordPayload;
|
||||
import ciomek.loon.mqtt.payload.RegisterResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.ArgumentTypeString;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilderRequired;
|
||||
import net.minecraft.core.entity.player.Player;
|
||||
import net.minecraft.core.net.command.CommandManager;
|
||||
import net.minecraft.core.net.command.CommandSource;
|
||||
|
||||
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
|
||||
|
||||
public class ChangePasswordCommand implements CommandManager.CommandRegistry {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
|
||||
commandDispatcher
|
||||
.register(
|
||||
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("change-password")
|
||||
.then(
|
||||
ArgumentBuilderRequired.argument("password", ArgumentTypeString.string())
|
||||
.executes(context -> {
|
||||
CommandSource source = (CommandSource) context.getSource();
|
||||
Player player = source.getSender();
|
||||
|
||||
if (player == null) {
|
||||
source.sendMessage("This command can only be run by a player!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
String password = ArgumentTypeString.getString(context, "password");
|
||||
|
||||
ChangePasswordPayload payload = new ChangePasswordPayload(player.uuid.toString(), password);
|
||||
|
||||
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
|
||||
|
||||
source.sendMessage("Sent request to the server! Waiting for the response. ");
|
||||
|
||||
MQTTClient.getInstance().subscribe("loon/register/+/response", (topic, message) -> {
|
||||
String[] parts = topic.split("/");
|
||||
|
||||
String playerUuid = parts[2];
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
RegisterResponse response = objectMapper.readValue(
|
||||
message.getPayload(),
|
||||
RegisterResponse.class
|
||||
);
|
||||
|
||||
if (response == null)
|
||||
return;
|
||||
|
||||
if (response.success())
|
||||
source.sendMessage("Successfully changed your password!");
|
||||
else
|
||||
{
|
||||
source.sendMessage("Failed to change your password!");
|
||||
source.sendMessage("Here's what went wrong:");
|
||||
source.sendMessage(response.error());
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package ciomek.loon.commands;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.RegisterPayload;
|
||||
import ciomek.loon.mqtt.payload.RegisterResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.ArgumentTypeString;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilderRequired;
|
||||
import net.minecraft.core.entity.player.Player;
|
||||
import net.minecraft.core.net.command.CommandManager;
|
||||
import net.minecraft.core.net.command.CommandSource;
|
||||
|
||||
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
|
||||
|
||||
public class RegisterCommand implements CommandManager.CommandRegistry {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
|
||||
commandDispatcher
|
||||
.register(
|
||||
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("register")
|
||||
.then(
|
||||
ArgumentBuilderRequired.argument("internalUsername", ArgumentTypeString.string())
|
||||
.then(
|
||||
ArgumentBuilderRequired.argument("password", ArgumentTypeString.string())
|
||||
.executes(context -> {
|
||||
CommandSource source = (CommandSource) context.getSource();
|
||||
Player player = source.getSender();
|
||||
|
||||
if (player == null) {
|
||||
source.sendMessage("This command can only be run by a player!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
String internalUsername = ArgumentTypeString.getString(context, "internalUsername");
|
||||
String password = ArgumentTypeString.getString(context, "password");
|
||||
|
||||
RegisterPayload payload = new RegisterPayload(player.uuid.toString(), password, internalUsername);
|
||||
|
||||
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
|
||||
|
||||
source.sendMessage("Sent request to the server! Waiting for the response. ");
|
||||
|
||||
MQTTClient.getInstance().subscribe("loon/register/+/response", (topic, message) -> {
|
||||
String[] parts = topic.split("/");
|
||||
|
||||
String playerUuid = parts[2];
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
RegisterResponse response = objectMapper.readValue(
|
||||
message.getPayload(),
|
||||
RegisterResponse.class
|
||||
);
|
||||
|
||||
if (response == null)
|
||||
return;
|
||||
|
||||
if (response.success())
|
||||
source.sendMessage("Successfully registered the user!");
|
||||
else
|
||||
{
|
||||
source.sendMessage("Failed to register the user!");
|
||||
source.sendMessage("Here's what went wrong:");
|
||||
source.sendMessage(response.error());
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerInventoryPayload;
|
||||
import net.minecraft.core.entity.player.Player;
|
||||
import net.minecraft.core.item.ItemStack;
|
||||
import net.minecraft.core.player.inventory.container.ContainerInventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(value = ContainerInventory.class, remap = false)
|
||||
public class ContainerInventoryMQTTMixin {
|
||||
@Shadow
|
||||
public Player player;
|
||||
@Shadow
|
||||
public ItemStack[] mainInventory;
|
||||
|
||||
@Shadow
|
||||
public @Nullable ItemStack @NotNull [] armorInventory;
|
||||
|
||||
@Unique
|
||||
private void sendFullInventory() {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new PlayerInventoryPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
Arrays.stream(mainInventory).map(ItemStackData::from).toList(),
|
||||
Arrays.stream(armorInventory).map(ItemStackData::from).toList()
|
||||
).send();
|
||||
}
|
||||
|
||||
@Inject(method = {
|
||||
"setItem",
|
||||
"insertItem",
|
||||
"sort",
|
||||
"dropAllItems",
|
||||
"transferAllContents",
|
||||
"load"
|
||||
}, at = @At("RETURN"))
|
||||
private void onInventoryChanged(CallbackInfo ci) {
|
||||
sendFullInventory();
|
||||
}
|
||||
|
||||
@Inject(method = {
|
||||
"removeItem",
|
||||
"consumeInventoryItem"
|
||||
}, at = @At("RETURN"))
|
||||
private void onInventoryChangedReturn(CallbackInfoReturnable<?> cir) {
|
||||
sendFullInventory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerPickUpItemPayload;
|
||||
import net.minecraft.core.entity.EntityItem;
|
||||
import net.minecraft.core.entity.player.Player;
|
||||
import net.minecraft.core.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = EntityItem.class, remap = false)
|
||||
public class EntityItemMQTTMixin {
|
||||
@Shadow
|
||||
public ItemStack item;
|
||||
|
||||
@Unique
|
||||
private int originalStackSize;
|
||||
|
||||
@Inject(method = "playerTouch", at = @At("HEAD"))
|
||||
public void onPlayerTouchHead(Player player, CallbackInfo ci) {
|
||||
originalStackSize = item != null ? item.stackSize : 0;
|
||||
}
|
||||
|
||||
@Inject(method = "playerTouch", at = @At("TAIL"))
|
||||
public void onPlayerTouch(Player player, CallbackInfo ci) {
|
||||
if (item == null || item.stackSize >= originalStackSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerPickUpItemPayload payload = new PlayerPickUpItemPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
ItemStackData.from(item, originalStackSize - item.stackSize)
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.CommandPayload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.list.PlayerKickedPayload;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerTeleportedPayload;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
import net.minecraft.server.net.handler.PacketHandlerServer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = PacketHandlerServer.class, remap = false)
|
||||
public class PacketHandlerServerMQTTMixin {
|
||||
@Shadow
|
||||
private PlayerServer playerEntity;
|
||||
|
||||
@Inject(method = "handleSlashCommand", at = @At("TAIL"))
|
||||
private void onSlashCommand(String command, CallbackInfo ci) {
|
||||
CommandPayload payload = new CommandPayload(command, playerEntity.username);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "kickPlayer", at = @At("HEAD"))
|
||||
private void onKickPlayer(String reason, CallbackInfo ci) {
|
||||
PlayerKickedPayload payload = new PlayerKickedPayload(
|
||||
new PlayerIdentity(playerEntity.uuid, playerEntity.username),
|
||||
reason
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "teleport(DDD)V", at = @At("HEAD"))
|
||||
private void onTeleport(double x, double y, double z, CallbackInfo ci) {
|
||||
publishTeleport(x, y, z, playerEntity.dimension);
|
||||
}
|
||||
|
||||
@Inject(method = "teleportAndRotate", at = @At("HEAD"))
|
||||
private void onTeleportAndRotate(double x, double y, double z, float yaw, float pitch, CallbackInfo ci) {
|
||||
publishTeleport(x, y, z, playerEntity.dimension);
|
||||
}
|
||||
|
||||
private void publishTeleport(double toX, double toY, double toZ, int dimension) {
|
||||
PlayerTeleportedPayload payload = new PlayerTeleportedPayload(
|
||||
new PlayerIdentity(playerEntity.uuid, playerEntity.username),
|
||||
playerEntity.x,
|
||||
playerEntity.y,
|
||||
playerEntity.z,
|
||||
toX,
|
||||
toY,
|
||||
toZ,
|
||||
dimension
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.player.list.PlayerJoinedPayload;
|
||||
import ciomek.loon.mqtt.payload.player.list.PlayerLeftPayload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
import net.minecraft.server.net.PlayerList;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = PlayerList.class, remap = false)
|
||||
public class PlayerListMQTTMixin {
|
||||
@Inject(method = "playerLoggedIn", at = @At(value = "TAIL"))
|
||||
public void onPayerLoggedIn(PlayerServer player, CallbackInfo ci) {
|
||||
PlayerJoinedPayload payload = new PlayerJoinedPayload(new PlayerIdentity(player.uuid, player.username));
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "playerLoggedOut", at = @At(value = "TAIL"))
|
||||
public void onPayerLoggedOut(PlayerServer entityplayermp, CallbackInfo ci) {
|
||||
PlayerLeftPayload payload = new PlayerLeftPayload(new PlayerIdentity(entityplayermp.uuid, entityplayermp.username));
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerDiedPayload;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerHurtPayload;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerDropItemPayload;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerInventoryPayload;
|
||||
import net.minecraft.core.entity.Entity;
|
||||
import net.minecraft.core.entity.player.Player;
|
||||
import net.minecraft.core.item.ItemStack;
|
||||
import net.minecraft.core.util.helper.DamageType;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(value = Player.class, remap = false)
|
||||
public class PlayerMQTTMixin {
|
||||
@Unique
|
||||
DamageType lastDamageType = null;
|
||||
|
||||
@Inject(method = "hurt", at = @At(value = "HEAD"))
|
||||
public void onHurtStart(Entity attacker, int damage, DamageType type, CallbackInfoReturnable<Boolean> cir) {
|
||||
lastDamageType = type;
|
||||
}
|
||||
|
||||
@Inject(method = "hurt", at = @At(value = "TAIL"))
|
||||
public void onHurt(Entity attacker, int damage, DamageType type, CallbackInfoReturnable<Boolean> cir) {
|
||||
Player player = (Player) (Object) this;
|
||||
|
||||
PlayerHurtPayload payload = new PlayerHurtPayload(new PlayerIdentity(player.uuid, player.username), damageTypeName(type));
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "onDeath", at = @At(value = "TAIL"))
|
||||
public void onDeath(Entity entityKilledBy, CallbackInfo ci) {
|
||||
Player player = (Player) (Object) this;
|
||||
|
||||
String killerName;
|
||||
if (entityKilledBy instanceof Player killer) {
|
||||
killerName = killer.username;
|
||||
} else if (entityKilledBy != null) {
|
||||
killerName = Entity.getNameFromEntity(entityKilledBy, true);
|
||||
} else {
|
||||
killerName = null;
|
||||
}
|
||||
|
||||
String damageType = lastDamageType == null ? null : damageTypeName(lastDamageType);
|
||||
PlayerDiedPayload payload = new PlayerDiedPayload(new PlayerIdentity(player.uuid, player.username), killerName, damageType);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "dropItem", at = @At("TAIL"))
|
||||
public void onDropItem(ItemStack stack, boolean randomDirection, CallbackInfo ci) {
|
||||
if (stack == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) (Object) this;
|
||||
PlayerDropItemPayload payload = new PlayerDropItemPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
ItemStackData.from(stack)
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
|
||||
@Inject(method = "setItemInArmorSlot", at = @At("TAIL"))
|
||||
private void onArmorChanged(CallbackInfo ci) {
|
||||
Player player = (Player) (Object) this;
|
||||
|
||||
new PlayerInventoryPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
Arrays.stream(player.inventory.mainInventory).map(ItemStackData::from).toList(),
|
||||
Arrays.stream(player.inventory.armorInventory).map(ItemStackData::from).toList()
|
||||
).send();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static String damageTypeName(DamageType type) {
|
||||
String key = type.getLanguageKey();
|
||||
return key.substring(key.lastIndexOf('.') + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package ciomek.loon.mixin;
|
||||
|
||||
import ciomek.loon.TPSTracker;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.data.PositionData;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerPositionPayload;
|
||||
import ciomek.loon.mqtt.request.IRequest;
|
||||
import ciomek.loon.mqtt.request.RequestManager;
|
||||
import net.minecraft.core.world.Dimension;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
import net.minecraft.server.world.WorldServer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Mixin(value = WorldServer.class, remap = false)
|
||||
public class WorldServerMixin {
|
||||
@Unique
|
||||
private static final int POSITION_CHECK_INTERVAL_TICKS = 20;
|
||||
|
||||
@Shadow
|
||||
public MinecraftServer mcServer;
|
||||
|
||||
@Unique
|
||||
private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
|
||||
|
||||
@Unique
|
||||
private static long lastTickNanos;
|
||||
|
||||
@Unique
|
||||
private static long gapSumNanos;
|
||||
|
||||
@Unique
|
||||
private static int gapTicks;
|
||||
|
||||
@Unique
|
||||
private static int positionCheckTicks;
|
||||
|
||||
@Unique
|
||||
private static final Map<UUID, PositionData> lastSentPositions = new HashMap<>();
|
||||
|
||||
@Inject(method = "tick", at = @At("TAIL"))
|
||||
private void onTick(CallbackInfo ci) {
|
||||
WorldServer self = (WorldServer) (Object) this;
|
||||
if (self.dimension == Dimension.OVERWORLD) {
|
||||
long now = System.nanoTime();
|
||||
if (lastTickNanos != 0) {
|
||||
gapSumNanos += now - lastTickNanos;
|
||||
gapTicks++;
|
||||
if (gapSumNanos >= 1_000_000_000L) {
|
||||
TPSTracker.setTPS(Math.min(20.0, gapTicks / (gapSumNanos / 1e9)));
|
||||
gapSumNanos = 0;
|
||||
gapTicks = 0;
|
||||
}
|
||||
}
|
||||
lastTickNanos = now;
|
||||
|
||||
if (++positionCheckTicks >= POSITION_CHECK_INTERVAL_TICKS) {
|
||||
positionCheckTicks = 0;
|
||||
broadcastMovedPlayerPositions();
|
||||
}
|
||||
}
|
||||
|
||||
IRequest request;
|
||||
while ((request = RequestManager.pollRequest()) != null) {
|
||||
IRequest current = request;
|
||||
EXECUTOR.submit(() -> current.handle(mcServer));
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void broadcastMovedPlayerPositions() {
|
||||
for (PlayerServer player : mcServer.playerList.playerEntities) {
|
||||
PositionData position = new PositionData(player.x, player.y, player.z);
|
||||
|
||||
if (!position.equals(lastSentPositions.put(player.uuid, position))) {
|
||||
PlayerPositionPayload payload = new PlayerPositionPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
position,
|
||||
player.dimension
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package ciomek.loon.mqtt;
|
||||
|
||||
import ciomek.loon.Loon;
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.request.*;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MQTTClient {
|
||||
private static MQTTClient instance;
|
||||
private MqttClient client;
|
||||
private MQTTSettings settings;
|
||||
|
||||
public static synchronized MQTTClient getInstance() {
|
||||
if(instance != null)
|
||||
return instance;
|
||||
|
||||
MQTTClient mqttClient = MQTTClient.init();
|
||||
instance = mqttClient;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void subscribe(String topicFilter, IMqttMessageListener messageListener) {
|
||||
try {
|
||||
client.subscribe(topicFilter, messageListener);
|
||||
} catch (MqttException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void publish(String topic, String message, int qos) {
|
||||
MqttMessage mqttMessage = new MqttMessage(message.getBytes());
|
||||
mqttMessage.setQos(qos);
|
||||
|
||||
try {
|
||||
client.publish(topic, mqttMessage);
|
||||
} catch (MqttException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void publish(String topic, String message) {
|
||||
publish(topic, message, 1);
|
||||
}
|
||||
|
||||
public void publish(String topic, Payload payload) {
|
||||
publish(topic, payload.toJson());
|
||||
}
|
||||
|
||||
|
||||
static MQTTClient init() {
|
||||
MQTTClient client = new MQTTClient();
|
||||
client.establish();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public void establish() {
|
||||
settings = MQTTSettings.getFromEnvVars();
|
||||
|
||||
String broker = settings.broker();
|
||||
String clientId = settings.clientId();
|
||||
String username = settings.username();
|
||||
String password = settings.password();
|
||||
|
||||
try {
|
||||
client = new MqttClient(broker, clientId);
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setUserName(username);
|
||||
options.setPassword(password.toCharArray());
|
||||
options.setMaxInflight(10000);
|
||||
|
||||
client.setCallback(new MqttCallback() {
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) {
|
||||
if (topic.matches("loon/player/[^/]+/inventory/full/request")) {
|
||||
Pattern pattern = Pattern.compile("loon/player/([^/]+)/inventory/full/request");
|
||||
Matcher matcher = pattern.matcher(topic);
|
||||
|
||||
if (matcher.matches()) {
|
||||
UUID playerId = UUID.fromString(matcher.group(1));
|
||||
|
||||
InventoryRequest inventoryRequest = new InventoryRequest(playerId);
|
||||
|
||||
RequestManager.addRequest(inventoryRequest);
|
||||
}
|
||||
}
|
||||
|
||||
if (topic.matches("loon/player/[^/]+/online/request")) {
|
||||
Pattern pattern = Pattern.compile("loon/player/([^/]+)/online/request");
|
||||
Matcher matcher = pattern.matcher(topic);
|
||||
|
||||
if (matcher.matches()) {
|
||||
UUID playerId = UUID.fromString(matcher.group(1));
|
||||
|
||||
PlayerOnlineRequest playerOnlineRequest = new PlayerOnlineRequest(playerId);
|
||||
|
||||
RequestManager.addRequest(playerOnlineRequest);
|
||||
}
|
||||
}
|
||||
|
||||
if (topic.matches("loon/player/[^/]+/position/request")) {
|
||||
Pattern pattern = Pattern.compile("loon/player/([^/]+)/position/request");
|
||||
Matcher matcher = pattern.matcher(topic);
|
||||
|
||||
if (matcher.matches()) {
|
||||
UUID playerId = UUID.fromString(matcher.group(1));
|
||||
|
||||
PlayerPositionRequest request = new PlayerPositionRequest(playerId);
|
||||
|
||||
RequestManager.addRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern.compile("loon/world/chunks/(-?\\d+):(-?\\d+)/(-?\\d+):(-?\\d+)/request");
|
||||
Matcher matcher = pattern.matcher(topic);
|
||||
|
||||
if (matcher.matches())
|
||||
{
|
||||
int xStart = Integer.parseInt(matcher.group(1));
|
||||
int xEnd = Integer.parseInt(matcher.group(2));
|
||||
int zStart = Integer.parseInt(matcher.group(3));
|
||||
int zEnd = Integer.parseInt(matcher.group(4));
|
||||
|
||||
ChunkMapRequest request = new ChunkMapRequest(xStart, xEnd, zStart, zEnd);
|
||||
|
||||
RequestManager.addRequest(request);
|
||||
}
|
||||
|
||||
if (topic.equals("loon/server/info/request")) {
|
||||
RequestManager.addRequest(new ServerInfoRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
client.connect(options);
|
||||
client.subscribe("loon/player/+/inventory/full/request");
|
||||
client.subscribe("loon/player/+/online/request");
|
||||
client.subscribe("loon/player/+/position/request");
|
||||
client.subscribe("loon/world/chunks/+/+/request");
|
||||
client.subscribe("loon/server/info/request");
|
||||
} catch (MqttException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ciomek.loon.mqtt;
|
||||
|
||||
import ciomek.loon.Loon;
|
||||
|
||||
public record MQTTSettings (
|
||||
String broker,
|
||||
String username,
|
||||
String password,
|
||||
String clientId
|
||||
)
|
||||
{
|
||||
public static MQTTSettings getFromEnvVars()
|
||||
{
|
||||
return new MQTTSettings(getEnvVar("broker"), getEnvVar("username"), getEnvVar("password"), getEnvVar("clientid"));
|
||||
}
|
||||
|
||||
static String getEnvVar(String name)
|
||||
{
|
||||
String fullName = Loon.EnvVarPrefix + name.toUpperCase();
|
||||
|
||||
String value = System.getenv(fullName);
|
||||
|
||||
if (value == null || value.isBlank())
|
||||
throw new IllegalArgumentException(
|
||||
"Missing environment variable: " + fullName + ". "
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ChangePasswordPayload (
|
||||
String uuid,
|
||||
String password
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/register/change_password");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record CommandPayload(
|
||||
String command,
|
||||
String source
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/commands", "loon/all");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Payload {
|
||||
ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
List<String> topics();
|
||||
|
||||
default String toJson() {
|
||||
try {
|
||||
MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
return MAPPER.writeValueAsString(this);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
default void send()
|
||||
{
|
||||
topics().forEach(topic -> {
|
||||
MQTTClient.getInstance().publish(
|
||||
topic,
|
||||
this
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RegisterPayload (
|
||||
String uuid,
|
||||
String password,
|
||||
String internalUsername
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/register/request");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
public record RegisterResponse(
|
||||
boolean success,
|
||||
String error
|
||||
) {}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ciomek.loon.mqtt.payload;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ServerInfoPayload(
|
||||
double tps,
|
||||
String version,
|
||||
String motd,
|
||||
boolean onlineMode,
|
||||
int sleepPercentage,
|
||||
int currentPlayerCount,
|
||||
int maxPlayerCount,
|
||||
int difficulty,
|
||||
boolean pvpOn,
|
||||
int viewDistance
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/server/info");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ciomek.loon.mqtt.payload.data;
|
||||
|
||||
public record BlockData(
|
||||
String blockName,
|
||||
int blockId,
|
||||
int color
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ciomek.loon.mqtt.payload.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ChunkData (
|
||||
List<List<BlockData>> map,
|
||||
String biome,
|
||||
int x,
|
||||
int z,
|
||||
boolean loaded
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ciomek.loon.mqtt.payload.data;
|
||||
|
||||
import net.minecraft.core.item.ItemStack;
|
||||
|
||||
public record ItemStackData(
|
||||
String itemName,
|
||||
int itemID,
|
||||
int itemCount
|
||||
) {
|
||||
public static ItemStackData from(ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return from(stack, stack.stackSize);
|
||||
}
|
||||
|
||||
public static ItemStackData from(ItemStack stack, int itemCount) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ItemStackData(stack.getDisplayName(), stack.itemID, itemCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ciomek.loon.mqtt.payload.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record PlayerIdentity
|
||||
(
|
||||
UUID uuid,
|
||||
String username
|
||||
) { }
|
||||
@@ -0,0 +1,7 @@
|
||||
package ciomek.loon.mqtt.payload.data;
|
||||
|
||||
public record PositionData (
|
||||
double x,
|
||||
double y,
|
||||
double z
|
||||
) {}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ciomek.loon.mqtt.payload.player;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerDiedPayload(
|
||||
PlayerIdentity player,
|
||||
String killer,
|
||||
String damageType
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/died");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ciomek.loon.mqtt.payload.player;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerHurtPayload (
|
||||
PlayerIdentity player,
|
||||
String damageType
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/hurt");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ciomek.loon.mqtt.payload.player;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerOnlinePayload(
|
||||
PlayerIdentity player,
|
||||
boolean online
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/online", "loon/player/" + player.uuid() + "/online/response");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ciomek.loon.mqtt.payload.player;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.data.PositionData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerPositionPayload(
|
||||
PlayerIdentity player,
|
||||
PositionData position,
|
||||
Integer dimension
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/position", "loon/player/" + player.uuid() + "/position/response");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ciomek.loon.mqtt.payload.player;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerTeleportedPayload(
|
||||
PlayerIdentity player,
|
||||
double fromX,
|
||||
double fromY,
|
||||
double fromZ,
|
||||
double toX,
|
||||
double toY,
|
||||
double toZ,
|
||||
int dimension
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/teleported");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ciomek.loon.mqtt.payload.player.inventory;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerDropItemPayload(
|
||||
PlayerIdentity player,
|
||||
ItemStackData itemStack
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/inventory/dropped");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ciomek.loon.mqtt.payload.player.inventory;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerInventoryPayload(
|
||||
PlayerIdentity player,
|
||||
List<ItemStackData> grid,
|
||||
List<ItemStackData> armor
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/inventory/full/response");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ciomek.loon.mqtt.payload.player.inventory;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerPickUpItemPayload (
|
||||
PlayerIdentity player,
|
||||
ItemStackData itemStack
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/player/" + player.uuid() + "/inventory/picked_up");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ciomek.loon.mqtt.payload.player.list;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerJoinedPayload(
|
||||
PlayerIdentity player
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/playerlist/joined", "loon/player/" + player.uuid() + "/joined");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ciomek.loon.mqtt.payload.player.list;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerKickedPayload(
|
||||
PlayerIdentity player,
|
||||
String reason
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/playerlist/kicked", "loon/player/" + player.uuid() + "/kicked");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ciomek.loon.mqtt.payload.player.list;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PlayerLeftPayload(
|
||||
PlayerIdentity player
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/playerlist/left", "loon/player/" + player.uuid() + "/left");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ciomek.loon.mqtt.payload.world.chunks;
|
||||
|
||||
import ciomek.loon.mqtt.payload.Payload;
|
||||
import ciomek.loon.mqtt.payload.data.ChunkData;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ChunkMapPayload(
|
||||
ChunkData chunk
|
||||
) implements Payload {
|
||||
@Override
|
||||
public List<String> topics() {
|
||||
return List.of("loon/world/chunk/" + chunk.x() + "/" + chunk.z(), "loon/world/chunk/" + chunk.x() + "/" + chunk.z() + "/response");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import ciomek.loon.mqtt.payload.data.BlockData;
|
||||
import ciomek.loon.mqtt.payload.data.ChunkData;
|
||||
import ciomek.loon.mqtt.payload.world.chunks.ChunkMapPayload;
|
||||
import net.minecraft.core.block.Block;
|
||||
import net.minecraft.core.block.material.MaterialColor;
|
||||
import net.minecraft.core.world.World;
|
||||
import net.minecraft.core.world.biome.Biome;
|
||||
import net.minecraft.core.world.chunk.Chunk;
|
||||
import net.minecraft.core.world.chunk.ChunkLoader;
|
||||
import net.minecraft.core.world.chunk.provider.ChunkProvider;
|
||||
import net.minecraft.core.world.pos.ChunkPos;
|
||||
import net.minecraft.core.world.pos.ChunkTilePos;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public record ChunkMapRequest(int xStart, int xEnd, int zStart, int zEnd) implements IRequest {
|
||||
@Override
|
||||
public void handle(MinecraftServer server) {
|
||||
World world = server.getDimensionWorld(0);
|
||||
ChunkProvider provider = world.getChunkProvider();
|
||||
ChunkLoader loader = world.getLevelStorage().getChunkLoader(world.dimension);
|
||||
|
||||
for (int x = xStart; x <= xEnd; x++) {
|
||||
for (int z = zStart; z <= zEnd; z++) {
|
||||
Chunk currentChunk = resolveChunk(world, provider, loader, x, z);
|
||||
|
||||
if (currentChunk == null) {
|
||||
ChunkData chunkData = new ChunkData(null, "", x, z, false);
|
||||
new ChunkMapPayload(chunkData).send();
|
||||
continue;
|
||||
}
|
||||
|
||||
String biomeName = getBiomeName(currentChunk);
|
||||
|
||||
List<List<BlockData>> chunkMap = new ArrayList<>();
|
||||
for (int chunkX = 0; chunkX < 16; chunkX++) {
|
||||
chunkMap.add(new ArrayList<>());
|
||||
|
||||
for (int chunkZ = 0; chunkZ < 16; chunkZ++) {
|
||||
int highestBlockY = Math.max(0, currentChunk.getHeightValue(chunkX, chunkZ) - 1);
|
||||
|
||||
Block block = currentChunk.getBlock(new ChunkTilePos(chunkX, highestBlockY, chunkZ));
|
||||
|
||||
int meta = currentChunk.getBlockMetadata(chunkX, highestBlockY, chunkZ);
|
||||
int rgb = MaterialColor.getColorFromIndex(
|
||||
MaterialColor.getColorIndexFromBlock(block, meta)
|
||||
);
|
||||
|
||||
BlockData blockData = new BlockData(block.getKey(), block.id(), rgb);
|
||||
|
||||
chunkMap.get(chunkX).add(blockData);
|
||||
}
|
||||
}
|
||||
|
||||
ChunkData chunkData = new ChunkData(chunkMap, biomeName, x, z, true);
|
||||
new ChunkMapPayload(chunkData).send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getBiomeName(Chunk chunk)
|
||||
{
|
||||
int surfaceY = Math.max(0, chunk.getHeightValue(8, 8) - 1);
|
||||
Biome biome = chunk.getBlockBiome(new ChunkTilePos(8, surfaceY, 8));
|
||||
return biome != null ? biome.getRegistryKey() : "?";
|
||||
}
|
||||
|
||||
Chunk resolveChunk(World world, ChunkProvider provider, ChunkLoader loader, int x, int z)
|
||||
{
|
||||
ChunkPos chunkPos = new ChunkPos(x, z);
|
||||
|
||||
if (provider.isChunkLoaded(chunkPos))
|
||||
return world.getChunk(chunkPos);
|
||||
|
||||
if (loader == null || !loader.chunkExists(world, x, z))
|
||||
return null;
|
||||
|
||||
try {
|
||||
return loader.loadChunk(world, x, z);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
|
||||
public interface IRequest
|
||||
{
|
||||
void handle(MinecraftServer server);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import ciomek.loon.mqtt.MQTTClient;
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerInventoryPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public record InventoryRequest(UUID playerUuid) implements IRequest {
|
||||
@Override
|
||||
public void handle(MinecraftServer server) {
|
||||
PlayerServer player = server.playerList.playerEntities.stream()
|
||||
.filter(playerServer -> playerServer.uuid.equals(playerUuid))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerInventoryPayload payload = new PlayerInventoryPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
Arrays.stream(player.inventory.mainInventory)
|
||||
.map(ItemStackData::from)
|
||||
.toList(),
|
||||
Arrays.stream(player.inventory.armorInventory)
|
||||
.map(ItemStackData::from)
|
||||
.toList()
|
||||
);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import ciomek.loon.mqtt.payload.data.ItemStackData;
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerOnlinePayload;
|
||||
import ciomek.loon.mqtt.payload.player.inventory.PlayerInventoryPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public record PlayerOnlineRequest(UUID playerUuid) implements IRequest {
|
||||
@Override
|
||||
public void handle(MinecraftServer server) {
|
||||
boolean online = server.playerList.playerEntities.stream()
|
||||
.anyMatch(p -> p.uuid.equals(playerUuid));
|
||||
|
||||
PlayerServer player = server.playerList.playerEntities.stream()
|
||||
.filter(p -> p.uuid.equals(playerUuid))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
PlayerIdentity identity;
|
||||
if(player == null) {
|
||||
identity = new PlayerIdentity(playerUuid, "");
|
||||
}
|
||||
else {
|
||||
identity = new PlayerIdentity(player.uuid, player.username);
|
||||
}
|
||||
|
||||
PlayerOnlinePayload payload = new PlayerOnlinePayload(
|
||||
identity,
|
||||
online);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import ciomek.loon.mqtt.payload.data.PlayerIdentity;
|
||||
import ciomek.loon.mqtt.payload.data.PositionData;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerOnlinePayload;
|
||||
import ciomek.loon.mqtt.payload.player.PlayerPositionPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.entity.player.PlayerServer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record PlayerPositionRequest(UUID playerUuid) implements IRequest {
|
||||
@Override
|
||||
public void handle(MinecraftServer server) {
|
||||
PlayerServer player = server.playerList.playerEntities.stream()
|
||||
.filter(p -> p.uuid.equals(playerUuid))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (player == null) {
|
||||
PlayerPositionPayload payload = new PlayerPositionPayload(
|
||||
new PlayerIdentity(playerUuid, null),
|
||||
null,
|
||||
null
|
||||
);
|
||||
payload.send();
|
||||
}
|
||||
else {
|
||||
PositionData playerPosition = new PositionData(player.x, player.y, player.z);
|
||||
|
||||
PlayerPositionPayload payload = new PlayerPositionPayload(
|
||||
new PlayerIdentity(player.uuid, player.username),
|
||||
playerPosition,
|
||||
player.dimension
|
||||
);
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class RequestManager {
|
||||
static ConcurrentLinkedQueue<IRequest> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public static void addRequest(IRequest request)
|
||||
{
|
||||
queue.add(request);
|
||||
}
|
||||
|
||||
public static IRequest pollRequest()
|
||||
{
|
||||
return queue.poll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ciomek.loon.mqtt.request;
|
||||
|
||||
import ciomek.loon.TPSTracker;
|
||||
import ciomek.loon.mixin.WorldServerMixin;
|
||||
import ciomek.loon.mqtt.payload.ServerInfoPayload;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
public record ServerInfoRequest() implements IRequest {
|
||||
@Override
|
||||
public void handle(MinecraftServer server) {
|
||||
ServerInfoPayload payload = new ServerInfoPayload(
|
||||
TPSTracker.getTPS(),
|
||||
server.getMinecraftVersion(),
|
||||
server.motd,
|
||||
server.onlineMode,
|
||||
server.sleepPercentage,
|
||||
server.playerList.playerEntities.size(),
|
||||
server.maxPlayers,
|
||||
server.difficulty,
|
||||
server.pvpOn,
|
||||
server.viewDistance);
|
||||
|
||||
payload.send();
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "loon",
|
||||
"version": "${version}",
|
||||
"name": "Example Mod",
|
||||
"description": "This mod aims to help new BTA modders.",
|
||||
"authors": [
|
||||
"Turnip Labs"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "",
|
||||
"sources": ""
|
||||
},
|
||||
"icon": "icon.png",
|
||||
"license": "CC0-1.0",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"ciomek.loon.Loon"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"loon.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=${fabricloader}",
|
||||
"minecraft": "*",
|
||||
"java": ">=${java}"
|
||||
},
|
||||
"suggests": {
|
||||
"modmenu": ">=${modmenu}"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "ciomek.loon.mixin",
|
||||
"compatibilityLevel": "JAVA_${java}",
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"mixins": [
|
||||
"ContainerInventoryMQTTMixin",
|
||||
"EntityItemMQTTMixin",
|
||||
"PacketHandlerServerMQTTMixin",
|
||||
"PlayerListMQTTMixin",
|
||||
"PlayerMQTTMixin",
|
||||
"WorldServerMixin"
|
||||
],
|
||||
"client": []
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user