Skin Improvements

This commit is contained in:
skippyall
2025-04-24 22:09:38 +02:00
parent c7ff6de42b
commit 1931848068
33 changed files with 235 additions and 323 deletions
@@ -2,7 +2,7 @@ package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.command.Command;
import io.github.skippyall.minions.module.command.Command;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.ModuleItem;
import net.minecraft.screen.ScreenHandlerType;
@@ -6,8 +6,8 @@ import io.github.skippyall.minions.input.TextInput;
import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.MinionItem;
import io.github.skippyall.minions.minion.MinionProfileUtils;
import net.minecraft.block.PlayerSkullBlock;
import net.minecraft.block.entity.SkullBlockEntity;
import io.github.skippyall.minions.minion.skin.SkinProvider;
import io.github.skippyall.minions.minion.skin.SkinProviders;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.item.ItemStack;
@@ -20,10 +20,18 @@ import java.util.Optional;
public class MinionLookGui extends SimpleGui {
private ItemStack minionItem;
private SkinProvider currentSkinProvider;
public MinionLookGui(ServerPlayerEntity player, ItemStack minionItem) {
super(ScreenHandlerType.GENERIC_9X3, player, false);
this.minionItem = minionItem;
this.currentSkinProvider = SkinProviders.NAME;
}
public void update() {
updateName();
updateSkin();
updateSkinProvider();
}
private void updateName() {
@@ -37,45 +45,50 @@ public class MinionLookGui extends SimpleGui {
}
private void updateSkin() {
setSlot(16, new GuiElementBuilder()
GuiElementBuilder builder = new GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
);
getData().getSkin(player.server).thenAccept(skin -> {
setSlot(16, new GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
.setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), skin))
.setCallback(this::cycleSkinProvider)
);
});
.setCallback(() -> currentSkinProvider.openSkinMenu(player).thenAccept(skin -> {
MinionItem.setData(getData().withSkin(skin), minionItem);
}));
if(MinionItem.getData(minionItem) != null && MinionItem.getData(minionItem).skin().isPresent()) {
builder.setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), getData().skin().get()));
}
setSlot(16, builder);
}
private void cycleSkinProvider() {
int currentId = SkinProviders.SKIN_PROVIDERS.getRawId(currentSkinProvider);
currentId++;
if(SkinProviders.SKIN_PROVIDERS.size() == currentId) {
currentId = 0;
}
currentSkinProvider = SkinProviders.SKIN_PROVIDERS.get(currentId);
updateSkinProvider();
}
private void updateSkinProvider() {
setSlot(25, new GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponentTypes.CUSTOM_NAME, Text.literal())
.setComponent(DataComponentTypes.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(this::cycleSkinProvider)
);
updateSkin();
}
private MinionData getData() {
return MinionItem.getData(minionItem);
return MinionItem.getDataOrDefault(minionItem);
}
public static void open(ServerPlayerEntity player, ItemStack minionItem) {
MinionLookGui gui = new MinionLookGui(player, minionItem);
gui.update();
gui.open();
}
public void openRenameGui(ServerPlayerEntity player, ItemStack minionItem) {
TextInput.input(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionName)
TextInput.inputSync(player, Text.translatable("minions.gui.look.rename.title"), "Minion", MinionProfileUtils::checkMinionNameWithoutPrefix)
.thenAccept(name -> {
MinionItem.setData(MinionItem.getDataOrDefault(minionItem).withName(name), minionItem);
MinionItem.setData(getData().withName(MinionProfileUtils.PREFIX + name), minionItem);
open();
});
}
@@ -1,6 +1,6 @@
package io.github.skippyall.minions.gui;
import io.github.skippyall.minions.command.Command;
import io.github.skippyall.minions.module.command.Command;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.ModuleItem;
import io.github.skippyall.minions.program.block.CodeBlock;
@@ -9,6 +9,7 @@ import net.minecraft.text.Text;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Predicate;
public class TextInput<T> extends AnvilInputGui {
private final GuiElementBuilder valid = new GuiElementBuilder()
@@ -18,11 +19,11 @@ public class TextInput<T> extends AnvilInputGui {
private final GuiElementBuilder invalid = new GuiElementBuilder()
.setItem(Items.REDSTONE_BLOCK);
private final Function<String, Result<T, Text>> parser;
private final Function<String, CompletableFuture<Result<T, Text>>> parser;
private final CompletableFuture<T> future;
private Result<T, Text> result;
public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function<String, Result<T, Text>> parser, CompletableFuture<T> future) {
public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function<String, CompletableFuture<Result<T, Text>>> parser, CompletableFuture<T> future) {
super(player, false);
setTitle(title);
setDefaultInputValue(defaultValue);
@@ -32,18 +33,26 @@ public class TextInput<T> extends AnvilInputGui {
updateConfirmButton(defaultValue);
}
public static <T> CompletableFuture<T> input(ServerPlayerEntity player, Text title, String defaultValue, Function<String, Result<T, Text>> parser) {
public static <T> CompletableFuture<T> inputSync(ServerPlayerEntity player, Text title, String defaultValue, Function<String, Result<T, Text>> parser) {
return input(player, title, defaultValue, (String string) -> CompletableFuture.completedFuture(parser.apply(string)));
}
public static <T> CompletableFuture<T> input(ServerPlayerEntity player, Text title, String defaultValue, Function<String, CompletableFuture<Result<T, Text>>> parser) {
CompletableFuture<T> future = new CompletableFuture<>();
new TextInput<>(player, title, defaultValue, parser, future).open();
return future;
}
public static CompletableFuture<String> inputString(ServerPlayerEntity player, Text title, String defaultValue) {
return inputSync(player, title, defaultValue, Result.Success::new);
}
public static CompletableFuture<Integer> inputInt(ServerPlayerEntity player, Text title, String defaultValue) {
return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Integer.valueOf(string), Text.translatable("minions.command.input.int.fail")));
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Integer.valueOf(string), Text.translatable("minions.command.input.int.fail")));
}
public static CompletableFuture<Float> inputFloat(ServerPlayerEntity player, Text title, String defaultValue) {
return input(player, title, defaultValue, string -> Result.wrapCustomError(() -> Float.valueOf(string), Text.translatable("minions.command.input.float.fail")));
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Float.valueOf(string), Text.translatable("minions.command.input.float.fail")));
}
@Override
@@ -52,13 +61,15 @@ public class TextInput<T> extends AnvilInputGui {
}
public void updateConfirmButton(String input) {
result = parser.apply(input);
if(result.isSuccess()) {
setSlot(AnvilScreenHandler.OUTPUT_ID, valid);
} else {
Text text = result.getErrorOrThrow();
setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text));
}
parser.apply(input).thenAccept(result -> {
this.result = result;
if(result.isSuccess()) {
setSlot(AnvilScreenHandler.OUTPUT_ID, valid);
} else {
Text text = result.getErrorOrThrow();
setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text));
}
});
}
@Override
@@ -69,9 +80,11 @@ public class TextInput<T> extends AnvilInputGui {
}
public void onConfirm() {
result.ifSuccess(success -> {
future.complete(success);
close();
});
if(result != null) {
result.ifSuccess(success -> {
future.complete(success);
close();
});
}
}
}
@@ -6,27 +6,25 @@ import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import eu.pb4.polymer.core.api.other.PolymerComponent;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.minion.skin.SkinProvider;
import io.github.skippyall.minions.minion.skin.SkinProviders;
import net.minecraft.component.ComponentType;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier;
import net.minecraft.util.Uuids;
import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, boolean isSpawned) {
public record MinionData(UUID uuid, String name, Optional<PropertyMap> skin, boolean isSpawned) {
public static final Codec<MinionData> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Uuids.CODEC.fieldOf("uuid").forGetter(MinionData::uuid),
Codec.STRING.fieldOf("name").forGetter(MinionData::name),
SkinProviders.CODEC.optionalFieldOf("skin", null).forGetter(MinionData::skin),
Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin),
Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned)
).apply(instance, MinionData::new)
);
@@ -34,7 +32,7 @@ public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, bo
public static final ComponentType<UUID> COMPONENT = Registry.register(Registries.DATA_COMPONENT_TYPE, Identifier.of(Minions.MOD_ID, "minion_data"), ComponentType.<UUID>builder().codec(Uuids.CODEC).build());
public static MinionData createDefault() {
return new MinionData(UUID.randomUUID(), "Minion", null, false);
return new MinionData(UUID.randomUUID(), MinionProfileUtils.newDefaultMinionName(), Optional.empty(), false);
}
public MinionData withUuid(UUID uuid) {
@@ -45,17 +43,10 @@ public record MinionData(UUID uuid, String name, @Nullable SkinProvider skin, bo
return new MinionData(uuid, name, skin, isSpawned);
}
public MinionData withSkin(@Nullable SkinProvider skin) {
public MinionData withSkin(Optional<PropertyMap> skin) {
return new MinionData(uuid, name, skin, isSpawned);
}
public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) {
if(skin != null) {
return skin.getSkin(server);
}
return CompletableFuture.completedFuture(null);
}
public MinionData withSpawned(boolean isSpawned) {
return new MinionData(uuid, name, skin, isSpawned);
}
@@ -2,18 +2,23 @@ package io.github.skippyall.minions.minion;
import eu.pb4.polymer.core.api.item.PolymerItem;
import eu.pb4.polymer.core.api.item.PolymerItemUtils;
import io.github.skippyall.minions.gui.MinionLookGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.item.Items;
import net.minecraft.item.tooltip.TooltipType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec2f;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.packettweaker.PacketContext;
@@ -49,6 +54,17 @@ public class MinionItem extends Item implements PolymerItem {
}
}
@Override
public ActionResult use(World world, PlayerEntity user, Hand hand) {
if(user instanceof ServerPlayerEntity serverPlayer) {
ItemStack stack = user.getStackInHand(hand);
MinionLookGui.open(serverPlayer, stack);
return ActionResult.SUCCESS;
}
return ActionResult.SUCCESS_SERVER;
}
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
if(!context.getWorld().isClient) {
@@ -61,6 +77,7 @@ public class MinionItem extends Item implements PolymerItem {
public static void setData(MinionData data, ItemStack item) {
item.set(MinionData.COMPONENT, data.uuid());
MinionPersistentState.INSTANCE.updateMinionData(data);
}
@Nullable
@@ -75,6 +92,7 @@ public class MinionItem extends Item implements PolymerItem {
MinionData data = getData(item);
if(data == null) {
data = MinionData.createDefault();
setData(data, item);
}
return data;
}
@@ -18,7 +18,6 @@ public class MinionPersistentState extends PersistentState {
public static MinionPersistentState INSTANCE;
private final Map<UUID, MinionData> minionData = new HashMap<>();
//private final List<UUID> minionUuids = new ArrayList<>();
@Override
public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
@@ -28,13 +27,6 @@ public class MinionPersistentState extends PersistentState {
}
nbt.put("minions", list);
/*NbtList uuids = new NbtList();
for(UUID uuid : minionUuids) {
NbtCompound compound = new NbtCompound();
compound.putUuid("uuid", uuid);
uuids.add(compound);
}
nbt.put("uuids", uuids);*/
return nbt;
}
@@ -43,39 +35,13 @@ public class MinionPersistentState extends PersistentState {
MinionPersistentState instance = new MinionPersistentState();
for(NbtElement element : list) {
if(element instanceof NbtCompound compound1) {
MinionData data = MinionData.readNbt((NbtCompound) element);
MinionData data = MinionData.readNbt(compound1);
instance.minionData.put(data.uuid(), data);
}
}
/*NbtList uuids = compound.getList("uuids", NbtElement.COMPOUND_TYPE);
for(NbtElement element : uuids) {
instance.minionUuids.add(((NbtCompound) element).getUuid("uuid"));
}*/
return instance;
}
/*public void addMinionUUID(UUID uuid) {
if(!minionUuids.contains(uuid)) {
minionUuids.add(uuid);
}
}*/
public void addMinion(MinionData data) {
System.out.println("add Minion " + data.name());
minionData.put(data.uuid(), data);
markDirty();
}
public void removeMinion(MinionData minionData) {
removeMinion(minionData.uuid());
}
public void removeMinion(UUID minionUUID) {
minionData.remove(minionUUID);
markDirty();
}
public MinionData getMinionData(UUID uuid) {
return minionData.get(uuid);
}
@@ -86,12 +52,17 @@ public class MinionPersistentState extends PersistentState {
public void updateMinionData(MinionData data) {
minionData.put(data.uuid(), data);
markDirty();
}
public boolean isMinion(UUID uuid) {
return minionData.containsKey(uuid);
}
public boolean isMinionNameTaken(String name) {
return minionData.values().stream().anyMatch(data -> data.name().equals(name));
}
public static void create(MinecraftServer server) {
INSTANCE = server.getWorld(World.OVERWORLD).getPersistentStateManager().getOrCreate(TYPE, "minion");
}
@@ -4,7 +4,9 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.ProfileLookupCallback;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.authlib.yggdrasil.ProfileResult;
import com.mojang.brigadier.StringReader;
import io.github.skippyall.minions.input.Result;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.text.Text;
import net.minecraft.util.StringHelper;
@@ -17,54 +19,7 @@ import java.util.concurrent.ForkJoinPool;
import static io.github.skippyall.minions.Minions.LOGGER;
public class MinionProfileUtils {
public static final String PREFIX = "#";
public static CompletableFuture<@Nullable GameProfile> lookupSkinOwnerProfile(MinecraftServer server, String username) {
CompletableFuture<GameProfile> future = new CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
server.getGameProfileRepo().findProfilesByNames(new String[]{username}, new ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(GameProfile found) {
LOGGER.info("SkinProfile: {}", found);
try {
getSkinOwnerProfile(server, found.getId()).thenAccept(future::complete);
} catch (Throwable ex) {
LOGGER.warn("Exception during Game Profile creation", ex);
}
}
@Override
public void onProfileLookupFailed(String profileName, Exception exception) {
LOGGER.warn("Lookup Error: ", exception);
future.complete(null);
}
});
} catch (Throwable e) {
LOGGER.warn("Failed to get UUID for username " + username, e);
future.complete(null);
}
});
return future;
}
public static CompletableFuture<@Nullable GameProfile> getSkinOwnerProfile(MinecraftServer server, @Nullable UUID uuid) {
CompletableFuture<GameProfile> future = new CompletableFuture<>();
future.completeAsync(() -> {
GameProfile profile = null;
if(uuid != null) {
ProfileResult result = server.getSessionService().fetchProfile(uuid, true);
if (result != null) {
profile = result.profile();
LOGGER.info("Full SkinProfile: {}", profile);
}
}
return profile;
});
return future;
}
public static final String PREFIX = "+";
public static GameProfile makeNewMinionProfile(UUID uuidMinion, String username, PropertyMap skin) {
if(uuidMinion == null) {
@@ -79,16 +34,34 @@ public class MinionProfileUtils {
return newProfile;
}
public static Result<String, Text> checkMinionName(String name) {
if(StringHelper.isValidPlayerName(PREFIX + name)) {
return new Result.Success<>(name);
} else {
return new Result.Error<>(Text.translatable("minions.generic.minion_name_too_long"));
public static Result<String, Text> checkMinionNameWithoutPrefix(String name) {
for(char c : name.toCharArray()) {
if(!StringReader.isAllowedInUnquotedString(c)) {
return new Result.Error<>(Text.translatable("minions.generic.name.invalid_char"));
}
}
if((PREFIX + name).length() > 16) {
return new Result.Error<>(Text.translatable("minions.generic.name.too_long"));
}
if(!StringHelper.isValidPlayerName(PREFIX + name)) {
return new Result.Error<>(Text.translatable("minions.generic.name.invalid"));
}
if(MinionPersistentState.INSTANCE.isMinionNameTaken(PREFIX + name)) {
return new Result.Error<>(Text.translatable("minions.generic.name.taken"));
}
return new Result.Success<>(name);
}
public static boolean isValidMinionName(String name) {
return checkMinionName(name).isSuccess();
public static String newDefaultMinionName() {
int i = 0;
while (MinionPersistentState.INSTANCE.isMinionNameTaken("+Minion" + i)) {
i++;
}
return "+Minion" + i;
}
public static boolean isMinion(UUID uuid) {
@@ -58,22 +58,22 @@ public class MinionFakePlayer extends ServerPlayerEntity {
private final ModuleInventory moduleInventory = new ModuleInventory();
private final MinionRuntime runtime = new MinionRuntime(this);
private MinionData data;
private final MinionData data;
public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) {
MinecraftServer server = level.getServer();
CompletableFuture<PropertyMap> future = data.getSkin(server);
PropertyMap skin = data.skin().orElse(null);
GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin);
doSpawn(data, profile, server, level, pos, rot);
future.thenAccept((skin) -> {
GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin);
MinionsTickExecutor.addExecuteOnNextTick(() -> doSpawn(data, profile, server, level, pos, rot));
});
}
private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) {
MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault());
MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault(), data);
MinionPersistentState.INSTANCE.updateMinionData(data.withSpawned(true));
if(pos != null && rot != null) {
instance.fixStartingPosition = () -> instance.refreshPositionAndAngles(pos.x, pos.y, pos.z, rot.x, rot.y);
@@ -95,14 +95,15 @@ public class MinionFakePlayer extends ServerPlayerEntity {
instance.getAbilities().flying = false;
}
public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli)
public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli, MinionData data)
{
return new MinionFakePlayer(server, level, profile, cli);
return new MinionFakePlayer(server, level, profile, cli, data);
}
private MinionFakePlayer(MinecraftServer server, ServerWorld worldIn, GameProfile profile, SyncedClientOptions cli)
private MinionFakePlayer(MinecraftServer server, ServerWorld worldIn, GameProfile profile, SyncedClientOptions cli, MinionData data)
{
super(server, worldIn, profile, cli);
this.data = data;
}
public boolean isProgrammable() {
@@ -162,9 +163,7 @@ public class MinionFakePlayer extends ServerPlayerEntity {
}));
}
data.withSpawned(false);
MinionPersistentState.INSTANCE.updateMinionData(data);
MinionPersistentState.INSTANCE.updateMinionData(data.withSpawned(false));
}
@Override
@@ -291,6 +290,10 @@ public class MinionFakePlayer extends ServerPlayerEntity {
return stack;
}
public MinionData getData() {
return data;
}
@Override
public void writeCustomDataToNbt(NbtCompound nbt) {
super.writeCustomDataToNbt(nbt);
@@ -0,0 +1,27 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.input.TextInput;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class Base64SkinProvider implements SkinProvider {
@Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) {
return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.base64.title"), "")
.thenApply(base64String -> {
PropertyMap map = new PropertyMap();
map.put("textures", new Property("textures", base64String));
return Optional.of(map);
});
}
@Override
public Text getDisplayName() {
return Text.translatable("minions.gui.look.skin.base64");
}
}
@@ -1,41 +0,0 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.properties.PropertyMap;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public abstract class CachedSkinProvider implements SkinProvider {
private @Nullable PropertyMap cache = null;
protected CachedSkinProvider(@Nullable PropertyMap cache) {
this.cache = cache;
}
public abstract CompletableFuture<@Nullable PropertyMap> fetchSkin(MinecraftServer server);
public @Nullable PropertyMap getCache() {
return cache;
}
public CompletableFuture<@Nullable PropertyMap> updateCache(MinecraftServer server) {
CompletableFuture<PropertyMap> future = fetchSkin(server);
future.thenAccept(result -> {
this.cache = result;
});
return future;
}
@Override
public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) {
CompletableFuture<PropertyMap> future = new CompletableFuture<>();
if(cache == null) {
fetchSkin(server).thenAccept(skin -> {
cache = skin;
future.complete(skin);
});
}
return CompletableFuture.completedFuture(cache);
}
}
@@ -1,33 +0,0 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class DirectSkinProvider implements SkinProvider {
private final PropertyMap propertyMap;
public static final Codec<DirectSkinProvider> CODEC = Codecs.GAME_PROFILE_PROPERTY_MAP.xmap(DirectSkinProvider::new, DirectSkinProvider::getPropertyMap);
public DirectSkinProvider(PropertyMap propertyMap) {
this.propertyMap = propertyMap;
}
public PropertyMap getPropertyMap() {
return propertyMap;
}
@Override
public CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server) {
return CompletableFuture.completedFuture(propertyMap);
}
@Override
public Codec<? extends SkinProvider> getCodec() {
return null;
}
}
@@ -1,43 +1,25 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.minion.MinionProfileUtils;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.Nullable;
import io.github.skippyall.minions.input.TextInput;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class NameSkinProvider extends CachedSkinProvider {
public static final Codec<NameSkinProvider> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("name").forGetter(NameSkinProvider::getName),
Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(NameSkinProvider::getCache)
).apply(instance, NameSkinProvider::new));
private final String name;
public NameSkinProvider(String name, @Nullable PropertyMap cache) {
super(cache);
this.name = name;
}
public NameSkinProvider(String name) {
this(name, null);
}
public String getName() {
return name;
public class NameSkinProvider implements SkinProvider {
@Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) {
return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.name.title"), "")
.thenCompose(SkullBlockEntity::fetchProfileByName)
.thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties));
}
@Override
public CompletableFuture<PropertyMap> fetchSkin(MinecraftServer server) {
return MinionProfileUtils.lookupSkinOwnerProfile(server, name).thenApply(gameProfile -> gameProfile != null ? gameProfile.getProperties() : null);
}
@Override
public Codec<? extends SkinProvider> getCodec() {
return null;
public Text getDisplayName() {
return Text.translatable("minions.gui.look.skin.name");
}
}
@@ -1,14 +1,14 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.Nullable;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public interface SkinProvider {
CompletableFuture<@Nullable PropertyMap> getSkin(MinecraftServer server);
CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player);
Codec<? extends SkinProvider> getCodec();
Text getDisplayName();
}
@@ -1,6 +1,5 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Lifecycle;
import io.github.skippyall.minions.Minions;
import net.minecraft.registry.Registry;
@@ -9,18 +8,18 @@ import net.minecraft.registry.SimpleRegistry;
import net.minecraft.util.Identifier;
public class SkinProviders {
public static final Registry<Codec<? extends SkinProvider>> REGISTRY = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable());
public static final Registry<SkinProvider> SKIN_PROVIDERS = new SimpleRegistry<>(RegistryKey.ofRegistry(Identifier.of(Minions.MOD_ID, "skin_providers")), Lifecycle.stable());
public static final Codec<SkinProvider> CODEC = REGISTRY.getCodec().dispatchStable(SkinProvider::getCodec, codec -> codec.fieldOf("data"));
public static NameSkinProvider NAME = register(new NameSkinProvider(), "name");
public static UUIDSkinProvider UUID = register(new UUIDSkinProvider(), "uuid");
public static Base64SkinProvider BASE64 = register(new Base64SkinProvider(), "base64");
public static <T extends Codec<? extends SkinProvider>> T register(T skinProvider, Identifier id) {
Registry.register(REGISTRY, id, skinProvider);
return skinProvider;
public static <T extends SkinProvider> T register(T skinProvider, String path) {
return Registry.register(SKIN_PROVIDERS, Identifier.of(Minions.MOD_ID, path), skinProvider);
}
public static void register() {
register(UUIDSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "uuid"));
register(NameSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "name"));
register(DirectSkinProvider.CODEC, Identifier.of(Minions.MOD_ID, "direct"));
}
}
@@ -1,51 +1,32 @@
package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.input.TextInput;
import io.github.skippyall.minions.minion.MinionProfileUtils;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Uuids;
import net.minecraft.util.dynamic.Codecs;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class UUIDSkinProvider extends CachedSkinProvider {
public static final Codec<UUIDSkinProvider> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("cache", null).forGetter(CachedSkinProvider::getCache),
Uuids.CODEC.fieldOf("uuid").forGetter(UUIDSkinProvider::getUuid)
).apply(instance, UUIDSkinProvider::new));
private final UUID uuid;
public UUIDSkinProvider(UUID uuid) {
this(null, uuid);
}
public UUIDSkinProvider(PropertyMap cache, UUID uuid) {
super(cache);
this.uuid = uuid;
}
public UUID getUuid() {
return uuid;
public class UUIDSkinProvider implements SkinProvider {
@Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) {
return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.uuid.title"), "")
.thenCompose(uuidString -> SkullBlockEntity.fetchProfileByUuid(UUID.fromString(uuidString)))
.thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties));
}
@Override
public CompletableFuture<PropertyMap> fetchSkin(MinecraftServer server) {
return MinionProfileUtils.getSkinOwnerProfile(server, uuid).thenApply(gameProfile -> {
if (gameProfile != null) {
return gameProfile.getProperties();
} else {
return null;
}
});
}
@Override
public Codec<? extends SkinProvider> getCodec() {
return CODEC;
public Text getDisplayName() {
return Text.translatable("minions.gui.look.skin.uuid");
}
}
@@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.function.Predicate;
@Mixin(EntityView.class)
public class EntityViewMixin {
public interface EntityViewMixin {
@ModifyArg(method = "getClosestPlayer(DDDDZ)Lnet/minecraft/entity/player/PlayerEntity;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/EntityView;getClosestPlayer(DDDDLjava/util/function/Predicate;)Lnet/minecraft/entity/player/PlayerEntity;"))
private @Nullable Predicate<Entity> addMinionPredicate(@Nullable Predicate<Entity> targetPredicate) {
Predicate<Entity> predicate = EntityViewMixinHelper.ADDITIONAL_PREDICATE.get();
@@ -52,7 +52,7 @@ public class PlayerListMixin {
@WrapOperation(method = "respawnPlayer", at = @At(value = "NEW", target = "(Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/server/world/ServerWorld;Lcom/mojang/authlib/GameProfile;Lnet/minecraft/network/packet/c2s/common/SyncedClientOptions;)Lnet/minecraft/server/network/ServerPlayerEntity;"))
public ServerPlayerEntity makePlayerForRespawn(MinecraftServer minecraftServer, ServerWorld serverLevel, GameProfile gameProfile, SyncedClientOptions clientInformation, Operation<ServerPlayerEntity> original, ServerPlayerEntity serverPlayer, boolean bl) {
if (serverPlayer instanceof MinionFakePlayer minion) {
return MinionFakePlayer.respawnFake(minecraftServer, serverLevel, gameProfile, clientInformation);
return MinionFakePlayer.respawnFake(minecraftServer, serverLevel, gameProfile, clientInformation, minion.getData());
}
return original.call(minecraftServer, serverLevel, gameProfile, clientInformation);
}
@@ -2,7 +2,7 @@ package io.github.skippyall.minions.module;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.command.CommandExecutor;
import io.github.skippyall.minions.module.command.CommandExecutor;
import io.github.skippyall.minions.input.TextInput;
import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.command.SimpleCommand;
import io.github.skippyall.minions.module.command.SimpleCommand;
import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.command.SimpleCommand;
import io.github.skippyall.minions.module.command.SimpleCommand;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.command.SimpleCommand;
import io.github.skippyall.minions.module.command.SimpleCommand;
import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
@@ -1,6 +1,6 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.command.Command;
import io.github.skippyall.minions.module.command.Command;
import io.github.skippyall.minions.program.block.CodeBlock;
import net.minecraft.item.ItemConvertible;
@@ -1,6 +1,6 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.command.Command;
import io.github.skippyall.minions.module.command.Command;
import io.github.skippyall.minions.program.block.CodeBlock;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.command.SimpleCommand;
import io.github.skippyall.minions.module.command.SimpleCommand;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.command.SimpleCommand;
import io.github.skippyall.minions.module.command.SimpleCommand;
import io.github.skippyall.minions.input.TextInput;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
@@ -1,7 +1,7 @@
package io.github.skippyall.minions.module;
import eu.pb4.polymer.core.api.item.SimplePolymerItem;
import io.github.skippyall.minions.command.Command;
import io.github.skippyall.minions.module.command.Command;
import io.github.skippyall.minions.program.block.CodeBlock;
import net.minecraft.item.Item;
@@ -1,4 +1,4 @@
package io.github.skippyall.minions.command;
package io.github.skippyall.minions.module.command;
import net.minecraft.item.Item;
import net.minecraft.text.Text;
@@ -1,4 +1,4 @@
package io.github.skippyall.minions.command;
package io.github.skippyall.minions.module.command;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.server.network.ServerPlayerEntity;
@@ -1,4 +1,4 @@
package io.github.skippyall.minions.command;
package io.github.skippyall.minions.module.command;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.item.Item;
@@ -8,6 +8,13 @@
"minions.gui.commands.title": "%s's Commands",
"minions.gui.modules.title": "%s's Modules",
"minions.gui.ok": "OK",
"minions.gui.look.skin.name": "Name",
"minions.gui.look.skin.name.title": "Enter a player name",
"minions.gui.look.skin.uuid": "UUID",
"minions.gui.look.skin.uuid.title": "Enter a player UUID",
"minions.gui.look.skin.base64": "Base64",
"minions.gui.look.skin.base64.title": "Enter a skin in base64 encoding",
"minions.gui.look.rename.title": "Enter a name",
"minions.command.input.int.fail": "Not an integer",
"minions.command.input.float.fail": "Not a number",
@@ -31,5 +38,9 @@
"item.minions.basic_upgrade_base": "Basic Upgrade Base",
"item.minions.advanced_upgrade_base": "Advanced Upgrade Base",
"minions.minion_item.tooltip": "Name: %s"
"minions.minion_item.tooltip": "Name: %s",
"minions.generic.name.invalid_char": "Name contains invalid character",
"minions.generic.name.too_long": "Name is too long",
"minions.generic.name.invalid": "Name is invalid",
"minions.generic.name.taken": "This name is already used"
}