diff --git a/common/src/main/java/net/anvilcraft/anvillib/AnvilLib.java b/common/src/main/java/net/anvilcraft/anvillib/AnvilLib.java index fbbccdc..dd0bfd5 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/AnvilLib.java +++ b/common/src/main/java/net/anvilcraft/anvillib/AnvilLib.java @@ -12,8 +12,11 @@ public class AnvilLib { public static final Logger LOGGER = LogManager.getLogger(); public static void initialize() { - Bus.MAIN.register(new ClientEventHandler()); - GeckoLib.initialize(); } + + public static void initializeClient() { + Bus.MAIN.register(new ClientEventHandler()); + } + } diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ClientEventHandler.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ClientEventHandler.java index f85519b..0379e2c 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ClientEventHandler.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ClientEventHandler.java @@ -2,6 +2,7 @@ package net.anvilcraft.anvillib.cosmetics; import java.io.File; import java.net.URI; +import java.util.Objects; import java.util.Map.Entry; import net.anvilcraft.anvillib.AnvilLib; @@ -9,25 +10,25 @@ import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmeticProvider; import net.anvilcraft.anvillib.event.AddEntityRenderLayersEvent; import net.anvilcraft.anvillib.event.Bus; import net.anvilcraft.anvillib.event.IEventBusRegisterable; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.entity.player.PlayerEntity; public class ClientEventHandler implements IEventBusRegisterable { + private void onAddLayers(AddEntityRenderLayersEvent ev) { for (Entry> skin : ev.skinMap().entrySet()) if (skin.getValue() instanceof PlayerEntityRenderer render) render.addFeature(new CosmeticFeatureRenderer(render, skin.getKey())); } - private void registerRemoteCosmetics() { - File gameDir = MinecraftClient.getInstance().runDirectory; - File cacheDir = new File(gameDir, "anvillibCache"); //TODO: use assets cache dir + public static void registerRemoteCosmetics(File assetsCache) { + File cacheDir = new File(Objects.requireNonNull(assetsCache), "anvillib"); try { URI playerBase = new URI("https://api.tilera.xyz/anvillib/data/players/"); URI cosmeticBase = new URI("https://api.tilera.xyz/anvillib/data/cosmetics/"); - CosmeticsManager.registerProvider(new RemoteCosmeticProvider(playerBase, cosmeticBase, cacheDir)); + URI capeBase = new URI("https://api.tilera.xyz/anvillib/data/capes/"); + CosmeticsManager.registerProvider(new RemoteCosmeticProvider(playerBase, cosmeticBase, capeBase, cacheDir)); } catch (Exception e) { AnvilLib.LOGGER.error(e); } @@ -36,6 +37,5 @@ public class ClientEventHandler implements IEventBusRegisterable { @Override public void registerEventHandlers(Bus bus) { bus.register(AddEntityRenderLayersEvent.class, this::onAddLayers); - this.registerRemoteCosmetics(); } } diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/CosmeticsManager.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/CosmeticsManager.java index 563b72e..51dd6a0 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/CosmeticsManager.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/CosmeticsManager.java @@ -16,6 +16,7 @@ import software.bernie.geckolib3.geo.render.built.GeoModel; public class CosmeticsManager { private static List providers = new ArrayList<>(); private static Map> cosmeticCache = new HashMap<>(); + private static Map capeCache = new HashMap<>(); private static Set activePlayers = new HashSet<>(); private static Map cachedModels = new ConcurrentHashMap<>(); private static Map cachedAnimations = new ConcurrentHashMap<>(); @@ -40,6 +41,10 @@ public class CosmeticsManager { List cosmetics = cosmeticCache.get(player); for (ICosmeticProvider provider : providers) { provider.addCosmetics(player, (cosmetic) -> cosmetics.add(cosmetic)); + if (!capeCache.containsKey(player)) { + Identifier cape = provider.getCape(player); + if (cape != null) capeCache.put(player, cape); + } } } @@ -56,6 +61,11 @@ public class CosmeticsManager { return cosmeticCache.get(uuid); } + public static Identifier getCape(UUID player) { + loadPlayer(player); + return capeCache.getOrDefault(player, null); + } + protected static GeoModel getModel(Identifier id) { return cachedModels.get(id); } diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ICosmeticProvider.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ICosmeticProvider.java index 00c4a26..8803474 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ICosmeticProvider.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/ICosmeticProvider.java @@ -3,8 +3,14 @@ package net.anvilcraft.anvillib.cosmetics; import java.util.UUID; import java.util.function.Consumer; +import net.minecraft.util.Identifier; + public interface ICosmeticProvider { boolean requestsRefresh(); void addCosmetics(UUID player, Consumer cosmeticAdder); + + default Identifier getCape(UUID player) { + return null; + } } diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/RemoteCosmeticProvider.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/RemoteCosmeticProvider.java index 5bf2991..836a6ee 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/RemoteCosmeticProvider.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/RemoteCosmeticProvider.java @@ -14,9 +14,11 @@ import net.anvilcraft.anvillib.AnvilLib; import net.anvilcraft.anvillib.cosmetics.ICosmetic; import net.anvilcraft.anvillib.cosmetics.ICosmeticProvider; import net.anvilcraft.anvillib.cosmetics.remote.model.CosmeticData; +import net.anvilcraft.anvillib.cosmetics.remote.thread.CapeLoaderThread; import net.anvilcraft.anvillib.cosmetics.remote.thread.CosmeticAssetsLoaderThread; import net.anvilcraft.anvillib.cosmetics.remote.thread.CosmeticLoaderThread; import net.anvilcraft.anvillib.cosmetics.remote.thread.PlayerCosmeticLoaderThread; +import net.minecraft.util.Identifier; import net.minecraft.util.Util; public class RemoteCosmeticProvider implements ICosmeticProvider { @@ -25,16 +27,21 @@ public class RemoteCosmeticProvider implements ICosmeticProvider { public final Map cosmetics = new ConcurrentHashMap<>(); public final Map> playerCosmetics = new ConcurrentHashMap<>(); + public final Map capes = new ConcurrentHashMap<>(); + public final Map playerCapes = new ConcurrentHashMap<>(); private final Map knownCosmetics = new ConcurrentHashMap<>(); + private final Map knownCapes = new ConcurrentHashMap<>(); private boolean dirty = false; public final URI playerBase; public final URI cosmeticBase; + public final URI capeBase; private final File cacheDir; - public RemoteCosmeticProvider(URI playerBase, URI cosmeticBase, File cacheDir) { + public RemoteCosmeticProvider(URI playerBase, URI cosmeticBase, URI capeBase, File cacheDir) { this.playerBase = playerBase; this.cosmeticBase = cosmeticBase; + this.capeBase = capeBase; this.cacheDir = cacheDir; } @@ -60,6 +67,13 @@ public class RemoteCosmeticProvider implements ICosmeticProvider { } } + @Override + public Identifier getCape(UUID player) { + if (!this.playerCapes.containsKey(player)) return null; + String cape = this.playerCapes.get(player); + return this.capes.getOrDefault(cape, null); + } + public void markDirty() { synchronized(this) { this.dirty = true; @@ -73,7 +87,7 @@ public class RemoteCosmeticProvider implements ICosmeticProvider { } public void loadCosmetic(String id) throws MalformedURLException { - if (this.cosmetics.containsKey(id) || knownCosmetics.containsKey(id)) return; + if (this.cosmetics.containsKey(id) || this.knownCosmetics.containsKey(id)) return; this.knownCosmetics.put(id, true); URI url = cosmeticBase.resolve(id); Util.getMainWorkerExecutor().execute(new CosmeticLoaderThread(url, this)); @@ -83,6 +97,13 @@ public class RemoteCosmeticProvider implements ICosmeticProvider { Util.getMainWorkerExecutor().execute(new CosmeticAssetsLoaderThread(cosmetic, data, this.cacheDir, this)); } + public void loadCape(String id) throws MalformedURLException { + if (this.capes.containsKey(id) || this.knownCapes.containsKey(id)) return; + this.knownCapes.put(id, true); + URI url = capeBase.resolve(id); + Util.getMainWorkerExecutor().execute(new CapeLoaderThread(id, url, this.cacheDir, this)); + } + public void failCosmeticLoading(String id) { AnvilLib.LOGGER.error("Cosmetic loading failed: {}", id); this.cosmetics.remove(id); diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/CapeData.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/CapeData.java new file mode 100644 index 0000000..d52bc0f --- /dev/null +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/CapeData.java @@ -0,0 +1,10 @@ +package net.anvilcraft.anvillib.cosmetics.remote.model; + +import com.google.gson.annotations.Expose; + +public class CapeData { + @Expose + public String id; + @Expose + public String url; +} diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/PlayerData.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/PlayerData.java index f4ef0d5..9be6aef 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/PlayerData.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/model/PlayerData.java @@ -11,5 +11,7 @@ public class PlayerData { public UUID uuid; @Expose public List cosmetics; + @Expose + public String cape; } diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CapeLoaderThread.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CapeLoaderThread.java new file mode 100644 index 0000000..b4dbbdb --- /dev/null +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CapeLoaderThread.java @@ -0,0 +1,58 @@ +package net.anvilcraft.anvillib.cosmetics.remote.thread; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +import com.google.common.hash.Hashing; + +import net.anvilcraft.anvillib.AnvilLib; +import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmeticProvider; +import net.anvilcraft.anvillib.cosmetics.remote.model.CapeData; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.texture.AbstractTexture; +import net.minecraft.client.texture.MissingSprite; +import net.minecraft.client.texture.PlayerSkinTexture; +import net.minecraft.client.texture.TextureManager; +import net.minecraft.util.Identifier; + +public class CapeLoaderThread extends FileDownloaderThread { + + private String id; + private File cacheDir; + private URI url; + private RemoteCosmeticProvider provider; + private TextureManager textureManager = MinecraftClient.getInstance().getTextureManager(); + + public CapeLoaderThread(String id, URI url, File cacheDir, RemoteCosmeticProvider provider) { + super("0.2.0"); + this.id = id; + this.url = url; + this.cacheDir = cacheDir; + this.provider = provider; + } + + @SuppressWarnings("deprecation") + @Override + public void run() { + CapeData data = null; + try { + data = this.loadJson(url, CapeData.class); + } catch (IOException e) { + AnvilLib.LOGGER.error("Can't load cape: {}", id, e); + return; + } + Identifier location = new Identifier("anvillib", "textures/cape/"+data.id); + String hash = Hashing.sha1().hashUnencodedChars(data.id).toString(); + AbstractTexture texture = this.textureManager.getOrDefault(location, MissingSprite.getMissingSpriteTexture()); + if (texture == MissingSprite.getMissingSpriteTexture()) { + File file = new File(this.cacheDir, hash.length() > 2 ? hash.substring(0, 2) : "xx"); + File file2 = new File(file, hash); + texture = new PlayerSkinTexture(file2, data.url, new Identifier("textures/block/dirt.png"), false, null); + this.textureManager.registerTexture(location, texture); + } + this.provider.capes.put(data.id, location); + this.provider.markDirty(); + } + +} diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CosmeticAssetsLoaderThread.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CosmeticAssetsLoaderThread.java index e36021e..e161980 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CosmeticAssetsLoaderThread.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/CosmeticAssetsLoaderThread.java @@ -76,6 +76,7 @@ public class CosmeticAssetsLoaderThread extends FileDownloaderThread { this.cosmetic.loadAnimations(animations, anim); } + @SuppressWarnings("deprecation") private void loadTexture(TextureData data) { String hash = Hashing.sha1().hashUnencodedChars(this.data.id).toString(); AbstractTexture texture = this.textureManager.getOrDefault(this.cosmetic.getTextureLocation(), MissingSprite.getMissingSpriteTexture()); diff --git a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/PlayerCosmeticLoaderThread.java b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/PlayerCosmeticLoaderThread.java index ae8a1f8..b016984 100644 --- a/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/PlayerCosmeticLoaderThread.java +++ b/common/src/main/java/net/anvilcraft/anvillib/cosmetics/remote/thread/PlayerCosmeticLoaderThread.java @@ -26,6 +26,10 @@ public class PlayerCosmeticLoaderThread extends FileDownloaderThread{ this.provider.loadCosmetic(id); this.provider.playerCosmetics.get(player.uuid).add(id); } + if (player.cape != null) { + this.provider.loadCape(player.cape); + this.provider.playerCapes.put(player.uuid, player.cape); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/common/src/main/java/net/anvilcraft/anvillib/mixin/client/AbstractClientPlayerEntityMixin.java b/common/src/main/java/net/anvilcraft/anvillib/mixin/client/AbstractClientPlayerEntityMixin.java new file mode 100644 index 0000000..21427ad --- /dev/null +++ b/common/src/main/java/net/anvilcraft/anvillib/mixin/client/AbstractClientPlayerEntityMixin.java @@ -0,0 +1,29 @@ +package net.anvilcraft.anvillib.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +import com.mojang.authlib.GameProfile; + +import net.anvilcraft.anvillib.cosmetics.CosmeticsManager; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +@Mixin(AbstractClientPlayerEntity.class) +public abstract class AbstractClientPlayerEntityMixin extends PlayerEntity { + public AbstractClientPlayerEntityMixin(World world, BlockPos pos, float yaw, GameProfile profile) { + super(world, pos, yaw, profile); + } + + /** + * @reason Custom capes & no Mojank capes + * @author tilera + */ + @Overwrite + public Identifier getCapeTexture() { + return CosmeticsManager.getCape(this.uuid); + } +} diff --git a/common/src/main/java/net/anvilcraft/anvillib/mixin/client/MinecraftClientMixin.java b/common/src/main/java/net/anvilcraft/anvillib/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..8dec090 --- /dev/null +++ b/common/src/main/java/net/anvilcraft/anvillib/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,18 @@ +package net.anvilcraft.anvillib.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.At; + +import net.anvilcraft.anvillib.cosmetics.ClientEventHandler; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.RunArgs; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + @Inject(at = @At("RETURN"), method = "") + public void init(RunArgs args, CallbackInfo info) { + ClientEventHandler.registerRemoteCosmetics(args.directories.assetDir); + } +} diff --git a/common/src/main/resources/anvillib-common.mixins.json b/common/src/main/resources/anvillib-common.mixins.json index a4c1dc8..0ea5eda 100644 --- a/common/src/main/resources/anvillib-common.mixins.json +++ b/common/src/main/resources/anvillib-common.mixins.json @@ -3,7 +3,10 @@ "package": "net.anvilcraft.anvillib.mixin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", - "client": [], + "client": [ + "client.MinecraftClientMixin", + "client.AbstractClientPlayerEntityMixin" + ], "mixins": [ "accessor.AnimatedGeoModelAccessor", "common.RecipeManagerMixin", diff --git a/fabric/src/main/java/net/anvilcraft/anvillib/AnvilLibFabric.java b/fabric/src/main/java/net/anvilcraft/anvillib/AnvilLibFabric.java index 4f54f4c..7dfdda2 100644 --- a/fabric/src/main/java/net/anvilcraft/anvillib/AnvilLibFabric.java +++ b/fabric/src/main/java/net/anvilcraft/anvillib/AnvilLibFabric.java @@ -1,10 +1,16 @@ package net.anvilcraft.anvillib; +import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ModInitializer; -public class AnvilLibFabric implements ModInitializer { +public class AnvilLibFabric implements ModInitializer, ClientModInitializer { @Override public void onInitialize() { AnvilLib.initialize(); } + + @Override + public void onInitializeClient() { + AnvilLib.initializeClient(); + } } diff --git a/fabric/src/main/java/net/anvilcraft/anvillib/mixin/client/EntityRenderDispatcherMixin.java b/fabric/src/main/java/net/anvilcraft/anvillib/mixin/fabric/client/EntityRenderDispatcherMixin.java similarity index 94% rename from fabric/src/main/java/net/anvilcraft/anvillib/mixin/client/EntityRenderDispatcherMixin.java rename to fabric/src/main/java/net/anvilcraft/anvillib/mixin/fabric/client/EntityRenderDispatcherMixin.java index eb9ee61..af61225 100644 --- a/fabric/src/main/java/net/anvilcraft/anvillib/mixin/client/EntityRenderDispatcherMixin.java +++ b/fabric/src/main/java/net/anvilcraft/anvillib/mixin/fabric/client/EntityRenderDispatcherMixin.java @@ -1,4 +1,4 @@ -package net.anvilcraft.anvillib.mixin.client; +package net.anvilcraft.anvillib.mixin.fabric.client; import java.util.Map; diff --git a/fabric/src/main/resources/anvillib.mixins.json b/fabric/src/main/resources/anvillib.mixins.json index c9cc7e5..c4ae914 100644 --- a/fabric/src/main/resources/anvillib.mixins.json +++ b/fabric/src/main/resources/anvillib.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "client": [ + "client.EntityRenderDispatcherMixin" ], "mixins": [ ], diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 0bb8442..216ef15 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -17,6 +17,9 @@ "entrypoints": { "main": [ "net.anvilcraft.anvillib.AnvilLibFabric" + ], + "client": [ + "net.anvilcraft.anvillib.AnvilLibFabric" ] }, "mixins": [ diff --git a/forge/src/main/java/net/anvilcraft/anvillib/AnvilLibForge.java b/forge/src/main/java/net/anvilcraft/anvillib/AnvilLibForge.java index 8526028..0d2b864 100644 --- a/forge/src/main/java/net/anvilcraft/anvillib/AnvilLibForge.java +++ b/forge/src/main/java/net/anvilcraft/anvillib/AnvilLibForge.java @@ -1,10 +1,17 @@ package net.anvilcraft.anvillib; +import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; @Mod(AnvilLib.MODID) public class AnvilLibForge { public AnvilLibForge() { AnvilLib.initialize(); } + + @SubscribeEvent + public void onClientInitialize(FMLClientSetupEvent event) { + AnvilLib.initializeClient(); + } }