feat: custom capes
This commit is contained in:
parent
5dee30ea9e
commit
c5de54cca7
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String, EntityRenderer<? extends PlayerEntity>> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import software.bernie.geckolib3.geo.render.built.GeoModel;
|
|||
public class CosmeticsManager {
|
||||
private static List<ICosmeticProvider> providers = new ArrayList<>();
|
||||
private static Map<UUID, List<ICosmetic>> cosmeticCache = new HashMap<>();
|
||||
private static Map<UUID, Identifier> capeCache = new HashMap<>();
|
||||
private static Set<UUID> activePlayers = new HashSet<>();
|
||||
private static Map<Identifier, GeoModel> cachedModels = new ConcurrentHashMap<>();
|
||||
private static Map<Identifier, AnimationFile> cachedAnimations = new ConcurrentHashMap<>();
|
||||
|
@ -40,6 +41,10 @@ public class CosmeticsManager {
|
|||
List<ICosmetic> 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);
|
||||
}
|
||||
|
|
|
@ -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<ICosmetic> cosmeticAdder);
|
||||
|
||||
default Identifier getCape(UUID player) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, RemoteCosmetic> cosmetics = new ConcurrentHashMap<>();
|
||||
public final Map<UUID, Set<String>> playerCosmetics = new ConcurrentHashMap<>();
|
||||
public final Map<String, Identifier> capes = new ConcurrentHashMap<>();
|
||||
public final Map<UUID, String> playerCapes = new ConcurrentHashMap<>();
|
||||
private final Map<String, Boolean> knownCosmetics = new ConcurrentHashMap<>();
|
||||
private final Map<String, Boolean> 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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -11,5 +11,7 @@ public class PlayerData {
|
|||
public UUID uuid;
|
||||
@Expose
|
||||
public List<String> cosmetics;
|
||||
@Expose
|
||||
public String cape;
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 = "<init>")
|
||||
public void init(RunArgs args, CallbackInfo info) {
|
||||
ClientEventHandler.registerRemoteCosmetics(args.directories.assetDir);
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package net.anvilcraft.anvillib.mixin.client;
|
||||
package net.anvilcraft.anvillib.mixin.fabric.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
"compatibilityLevel": "JAVA_17",
|
||||
"minVersion": "0.8",
|
||||
"client": [
|
||||
"client.EntityRenderDispatcherMixin"
|
||||
],
|
||||
"mixins": [
|
||||
],
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
"entrypoints": {
|
||||
"main": [
|
||||
"net.anvilcraft.anvillib.AnvilLibFabric"
|
||||
],
|
||||
"client": [
|
||||
"net.anvilcraft.anvillib.AnvilLibFabric"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue