Begin adding DynamicChest UI, and finish adding the HeadUtilities

This commit is contained in:
Aria 2023-03-01 02:19:47 -07:00
parent 5c70fb1291
commit 8fde794b65
23 changed files with 662 additions and 7 deletions

View file

@ -13,14 +13,18 @@ import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
import dev.zontreck.libzontreck.commands.Commands;
import dev.zontreck.libzontreck.events.ForgeEventHandlers;
import dev.zontreck.libzontreck.events.PlayerChangedPositionEvent;
import dev.zontreck.libzontreck.events.ProfileLoadedEvent;
import dev.zontreck.libzontreck.memory.PlayerContainer;
import dev.zontreck.libzontreck.memory.VolatilePlayerStorage;
import dev.zontreck.libzontreck.networking.ModMessages;
import dev.zontreck.libzontreck.profiles.Profile;
import dev.zontreck.libzontreck.types.ModMenuTypes;
import dev.zontreck.libzontreck.util.DelayedExecutorService;
import dev.zontreck.libzontreck.util.FileTreeDatastore;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.common.MinecraftForge;
@ -32,6 +36,7 @@ import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
@ -45,6 +50,10 @@ public class LibZontreck {
public static boolean ALIVE;
public static final String FILESTORE = FileTreeDatastore.get();
public static final Path BASE_CONFIG;
public static final String PLAYER_INFO_URL = "https://api.mojang.com/users/profiles/minecraft/";
public static final String PLAYER_SKIN_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
static{
PROFILES = new HashMap<>();
@ -69,10 +78,12 @@ public class LibZontreck {
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(new ForgeEventHandlers());
MinecraftForge.EVENT_BUS.register(new Commands());
}
private void setup(final FMLCommonSetupEvent event)
{
ModMessages.register();
}
@ -97,4 +108,14 @@ public class LibZontreck {
}
}
@Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public static class ClientModEvents
{
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent ev)
{
//MenuScreens.register(ModMenuTypes.CHESTGUI.get(), ChestGuiScreen::new);
}
}
}

View file

@ -0,0 +1,17 @@
package dev.zontreck.libzontreck.commands;
import dev.zontreck.libzontreck.LibZontreck;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
@EventBusSubscriber(modid=LibZontreck.MOD_ID, bus=Mod.EventBusSubscriber.Bus.FORGE)
public class Commands {
@SubscribeEvent
public void onCommandsRegister(RegisterCommandsEvent ev)
{
CreditsCommand.register(ev.getDispatcher());
GetHead.register(ev.getDispatcher());
}
}

View file

@ -0,0 +1,30 @@
package dev.zontreck.libzontreck.commands;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.PlayerHeadItem;
public class CreditsCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatch)
{
dispatch.register(Commands.literal("aria_credits").executes(s->credits(s.getSource())));
}
private static int credits(CommandSourceStack source) {
// Open the credits GUI
if(source.getEntity() instanceof Player)
{
// OK.
ServerPlayer play = (ServerPlayer)source.getEntity();
return 0;
}else return 1;
}
}

View file

@ -0,0 +1,37 @@
package dev.zontreck.libzontreck.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.zontreck.libzontreck.util.HeadUtilities;
import dev.zontreck.libzontreck.util.HeadCache.HeadCacheItem;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.PlayerHeadItem;
public class GetHead {
public static void register(CommandDispatcher<CommandSourceStack> dispatch)
{
dispatch.register(Commands.literal("aria_debug_get_head").executes(c-> getHead(c.getSource(), c.getSource().getEntity().getName().getContents())).then(Commands.argument("name", StringArgumentType.string()).executes(c -> getHead(c.getSource(), StringArgumentType.getString(c, "name")))));
}
private static int getHead(CommandSourceStack source, String string) {
try {
ServerPlayer player= source.getPlayerOrException();
ItemStack head = HeadUtilities.get(string);
player.addItem(head);
} catch (CommandSyntaxException e) {
e.printStackTrace();
}
return 0;
}
}

View file

@ -0,0 +1,48 @@
package dev.zontreck.libzontreck.dynamicchest;
import dev.zontreck.libzontreck.networking.structures.OpenGUIRequest;
import dev.zontreck.libzontreck.types.ModMenuTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.SlotItemHandler;
public class ChestGuiMenu extends AbstractContainerMenu
{
public final Player player;
public ChestGuiMenu(int id, Inventory player)
{
this(id, player, new ItemStackHandler(36), BlockPos.ZERO, player.player, null);
}
public ChestGuiMenu(int id, Inventory player, IItemHandler handler, BlockPos pos, Player play, OpenGUIRequest request)
{
super(ModMenuTypes.CHESTGUI.get(), id);
this.player=play;
int slotSize=18;
int startX=11;
int startY=11;
for(int row=0; row<4; row++)
{
for(int column = 0; column<9;column++)
{
addSlot(new SlotItemHandler(handler, row * 9 + column, startX + column * slotSize, startY + row * slotSize));
}
}
}
@Override
public boolean stillValid(Player pPlayer) {
return true; // This is dynamic. We have no block entity!
}
}

View file

@ -0,0 +1,5 @@
package dev.zontreck.libzontreck.dynamicchest;
public class ChestGuiScreen {
}

View file

@ -0,0 +1,54 @@
package dev.zontreck.libzontreck.networking;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.networking.packets.ChestGUIOpenC2S;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
/**
* Networking system!
*/
public class ModMessages {
private static SimpleChannel INSTANCE;
private static int PACKET_ID=0;
private static int id()
{
return PACKET_ID++;
}
public static void register()
{
SimpleChannel net = NetworkRegistry.ChannelBuilder.named(new ResourceLocation(LibZontreck.MOD_ID, "messages"))
.networkProtocolVersion(()->"1.0")
.clientAcceptedVersions(s->true)
.serverAcceptedVersions(s->true)
.simpleChannel();
INSTANCE=net;
net.messageBuilder(ChestGUIOpenC2S.class, id(), NetworkDirection.PLAY_TO_SERVER)
.decoder(ChestGUIOpenC2S::new)
.encoder(ChestGUIOpenC2S::toBytes)
.consumer(ChestGUIOpenC2S::handle)
.add();
}
public static <MSG> void sendToServer(MSG message){
INSTANCE.sendToServer(message);
}
public static <MSG> void sendToPlayer(MSG message, ServerPlayer player)
{
INSTANCE.send(PacketDistributor.PLAYER.with(()->player), message);
}
public static <MSG> void sendToAll(MSG message)
{
INSTANCE.send(PacketDistributor.ALL.noArg(), message);
}
}

View file

@ -0,0 +1,21 @@
About Chest GUI
=====
A chest GUI is basically a dynamic menu that uses items and the standard chest layout to present a list of options in game.
These items cannot be removed from the chest and the click event is instead passed on as a ChestGUIEvent. Because the mod requesting this might not be on the client, the event is sent in both locations by utilizing a network packet.
ChestGUIEvent
====
This event is the parent of several other events.
OptionInteractEvent
----
This event gets dispatched on both the client and server when a option is interacted with.
OptionUpdateEvent
----
To be sent by the mod originating this dynamic menu. This event will instruct the ChestGUI to update a item, or multiple items after a interaction, or something else occuring. If the GUI is not open, this event gets ignored. This event should only be sent when we know the GUI is actually open!

View file

@ -0,0 +1,43 @@
package dev.zontreck.libzontreck.networking.packets;
import java.util.function.Supplier;
import dev.zontreck.libzontreck.networking.structures.OpenGUIRequest;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
/**
* To be used by first-party and third-party mods to assemble a menu
* NOTE: Without the server, only the credits menu will be able to be opened, which is the only built-in menu utilizing this system.
*/
public class ChestGUIOpenC2S {
private CompoundTag data;
public ChestGUIOpenC2S(OpenGUIRequest request)
{
data = request.serialize();
}
public ChestGUIOpenC2S(FriendlyByteBuf buf)
{
data = buf.readAnySizeNbt();
}
public void toBytes(FriendlyByteBuf buf)
{
buf.writeNbt(data);
}
public boolean handle(Supplier<NetworkEvent.Context> supplier)
{
NetworkEvent.Context ctx = supplier.get();
ctx.enqueueWork(()->{
// We are on the server!
OpenGUIRequest req = new OpenGUIRequest(data);
});
return true;
}
}

View file

@ -0,0 +1,48 @@
package dev.zontreck.libzontreck.networking.structures;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
public class OpenGUIRequest {
public List<ItemStack> options = new ArrayList<>();
public String GUITitle;
public UUID playerID;
public OpenGUIRequest(CompoundTag tag)
{
ListTag items = tag.getList("items", Tag.TAG_COMPOUND);
for(Tag tags : items)
{
ItemStack is = ItemStack.of((CompoundTag)tags);
options.add(is);
}
GUITitle = tag.getString("title");
playerID = tag.getUUID("player");
}
public CompoundTag serialize()
{
CompoundTag tag = new CompoundTag();
tag.putString("title", GUITitle);
tag.putUUID("player", playerID);
ListTag lst = new ListTag();
for (ItemStack itemStack : options) {
lst.add(itemStack.serializeNBT());
}
tag.put("items", lst);
return tag;
}
public OpenGUIRequest(){}
}

View file

@ -0,0 +1,23 @@
package dev.zontreck.libzontreck.types;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.dynamicchest.ChestGuiMenu;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModMenuTypes {
public static DeferredRegister<MenuType<?>> REGISTER = DeferredRegister.create(ForgeRegistries.CONTAINERS, LibZontreck.MOD_ID);
public static final RegistryObject<MenuType<ChestGuiMenu>> CHESTGUI = REGISTER.register("dynchest", ()->new MenuType<>(ChestGuiMenu::new));
public static void register(IEventBus bus)
{
REGISTER.register(bus);
}
}

View file

@ -5,6 +5,7 @@ import java.util.UUID;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.chat.ChatColor;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
@ -74,6 +75,16 @@ public class ChatHelpers {
broadcastTo(ID.getUUID(), message, server, true);
}
/**
* Returns the output with colors applied, and chat entries replaced using [number] as the format
* @param input
* @param inputs Entries to replace with in input
* @return
*/
public static TextComponent macro(String input, String... inputs)
{
return new TextComponent(macroize(input,inputs));
}
/**
* Returns the output with colors applied, and chat entries replaced using [number] as the format
* @param input

View file

@ -0,0 +1,184 @@
package dev.zontreck.libzontreck.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import dev.zontreck.libzontreck.LibZontreck;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntArrayTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.PlayerHeadItem;
public class HeadCache
{
public static final Path CACHE_FILE;
public static final HeadCache CACHE = new HeadCache();
public List<HeadCacheItem> items = new ArrayList<>();
public class HeadCacheItem
{
public UUID owner;
public String texture;
public String name;
public CompoundTag serialize()
{
CompoundTag tag = new CompoundTag();
tag.putUUID("id", owner);
tag.putString("texture", texture);
tag.putString("name", name);
return tag;
}
public HeadCacheItem(CompoundTag tag)
{
owner = tag.getUUID("id");
texture = tag.getString("texture");
name = tag.getString("name");
}
private HeadCacheItem()
{}
public ItemStack getAsItem()
{
ItemStack head = new ItemStack(Items.PLAYER_HEAD, 1);
CompoundTag skullOwner = new CompoundTag();
skullOwner.putUUID("Id", owner);
CompoundTag properties = new CompoundTag();
ListTag textures = new ListTag();
CompoundTag item = new CompoundTag();
item.putString("Value", texture);
textures.add(item);
properties.put("textures", textures);
skullOwner.put("Properties", properties);
head.addTagElement(PlayerHeadItem.TAG_SKULL_OWNER, skullOwner);
TextComponent headname = ChatHelpers.macro("!Dark_red![0]'s Head", name);
head.setHoverName(headname);
return head;
}
public static UUID toNewID(final String input) {
return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
}
public String getOldID()
{
return owner.toString().replaceAll("-", "");
}
}
static{
CACHE_FILE = LibZontreck.BASE_CONFIG.resolve("head_cache.nbt");
if(CACHE_FILE.toFile().exists())
{
// Deserialize heads
try {
CompoundTag tag = NbtIo.read(CACHE_FILE.toFile());
CACHE.initFromCache(tag);
} catch (IOException e) {
e.printStackTrace();
}
}else {
CACHE.resetCache();
}
}
private void initFromCache(CompoundTag tag)
{
ListTag heads = tag.getList("heads", Tag.TAG_COMPOUND);
for (Tag tag2 : heads) {
CompoundTag tag3 = (CompoundTag)tag2;
items.add(new HeadCacheItem(tag3));
}
}
public void saveCache()
{
try {
NbtIo.write(serialize(), CACHE_FILE.toFile());
} catch (IOException e) {
e.printStackTrace();
}
}
public CompoundTag serialize()
{
ListTag heads = new ListTag();
for (HeadCacheItem item : items) {
heads.add(item.serialize());
}
CompoundTag tag = new CompoundTag();
tag.put("heads", heads);
return tag;
}
/**
* Adds a new head to the cache
* @param owner
* @param texture
* @param name
* @return The item added to the cache
* @return Null if not added!
*/
public HeadCacheItem addToCache(UUID owner, String texture, String name)
{
HeadCacheItem item = new HeadCacheItem();
item.name=name;
item.texture=texture;
item.owner=owner;
if(!hasHead(name))
{
items.add(item);
saveCache();
return item;
}
return null;
}
/**
* Initializes the cache fresh using the default heads for the developer(s), contributors/testers, and patreon supporters
*/
public void resetCache()
{
HeadUtilities.get("zontreck");
HeadUtilities.get("PossumTheWarrior");
HeadUtilities.get("GemMD");
}
public boolean hasHead(String playerName)
{
for (HeadCacheItem item : items) {
if(item.name.equals(playerName)) return true;
}
return false;
}
public HeadCacheItem getHead(String playerName)
{
for (HeadCacheItem item : items) {
if(item.name.equals(playerName)) return item;
}
return null;
}
}

View file

@ -0,0 +1,67 @@
package dev.zontreck.libzontreck.util;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Scanner;
import java.util.UUID;
import com.google.gson.Gson;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.util.HeadCache.HeadCacheItem;
import net.minecraft.world.item.ItemStack;
/**
* Added to showcase Patreon supporters and those who have helped test or provide feedback and suggestions!
*/
public class HeadUtilities {
private static HeadCacheItem cachedLookup(String playerName)
{
if(HeadCache.CACHE.hasHead(playerName))
{
HeadCacheItem item = HeadCache.CACHE.getHead(playerName);
return item;
}else {
// Look up head then add to cache
return externalHeadRequest(playerName);
}
}
public static ItemStack get(String playerName)
{
return cachedLookup(playerName).getAsItem();
}
private static HeadCacheItem externalHeadRequest(String playerName)
{
String data="";
try {
data = HttpHelper.getFrom(new URL(LibZontreck.PLAYER_INFO_URL + playerName));
} catch (MalformedURLException e) {
e.printStackTrace();
}
if(data.equals("")){
return null;
}
UUID PlayerID = null;
String playerTexture="";
try{
Gson gson = new Gson();
PlayerInfo info = gson.fromJson(data, PlayerInfo.class);
String data2 = HttpHelper.getFrom(new URL(LibZontreck.PLAYER_SKIN_URL + info.id));
PlayerProfileInfo info2 = gson.fromJson(data2, PlayerProfileInfo.class);
playerTexture = info2.properties.get(0).value;
PlayerID = HeadCache.HeadCacheItem.toNewID(info.id);
return HeadCache.CACHE.addToCache(PlayerID, playerTexture, playerName);
}catch(Exception e)
{
return null;
}
}
}

View file

@ -0,0 +1,22 @@
package dev.zontreck.libzontreck.util;
import java.net.URL;
import java.util.Scanner;
public class HttpHelper {
public static String getFrom(URL url)
{
String data = "";
try (Scanner s = new Scanner(url.openStream()))
{
s.useDelimiter("\\A");
data = s.hasNext() ? s.next() : "";
}catch(Exception e)
{
}
return data;
}
}

View file

@ -0,0 +1,8 @@
package dev.zontreck.libzontreck.util;
import java.util.UUID;
public class PlayerInfo {
public String name;
public String id;
}

View file

@ -0,0 +1,9 @@
package dev.zontreck.libzontreck.util;
import java.util.List;
public class PlayerProfileInfo {
public String id;
public String name;
public List<PlayerTextureContainer> properties;
}

View file

@ -0,0 +1,6 @@
package dev.zontreck.libzontreck.util;
public class PlayerTextureContainer {
public String name;
public String value;
}