feat: remote cosmetics
This commit is contained in:
parent
490f4a7896
commit
dbfb64a112
|
@ -1,10 +1,15 @@
|
|||
package net.anvilcraft.anvillib.cosmetics;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import net.anvilcraft.anvillib.AnvilLib;
|
||||
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;
|
||||
|
@ -16,8 +21,21 @@ public class ClientEventHandler implements IEventBusRegisterable {
|
|||
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
|
||||
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));
|
||||
} catch (Exception e) {
|
||||
AnvilLib.LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEventHandlers(Bus bus) {
|
||||
bus.register(AddEntityRenderLayersEvent.class, this::onAddLayers);
|
||||
this.registerRemoteCosmetics();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
package net.anvilcraft.anvillib.cosmetics;
|
||||
|
||||
import net.anvilcraft.anvillib.mixin.accessor.AnimatedGeoModelAccessor;
|
||||
import net.minecraft.util.Identifier;
|
||||
import software.bernie.geckolib3.core.IAnimatable;
|
||||
import software.bernie.geckolib3.core.builder.Animation;
|
||||
import software.bernie.geckolib3.file.AnimationFile;
|
||||
import software.bernie.geckolib3.geo.exception.GeckoLibException;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoBone;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoModel;
|
||||
import software.bernie.geckolib3.model.AnimatedGeoModel;
|
||||
import software.bernie.geckolib3.resource.GeckoLibCache;
|
||||
|
||||
public class CosmeticModel extends AnimatedGeoModel<CosmeticItem> {
|
||||
@Override
|
||||
|
@ -18,4 +26,40 @@ public class CosmeticModel extends AnimatedGeoModel<CosmeticItem> {
|
|||
public Identifier getTextureLocation(CosmeticItem animatable) {
|
||||
return animatable.getCosmetic().getTextureLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animation getAnimation(String name, IAnimatable animatable) {
|
||||
Identifier location = ((CosmeticItem)animatable).getCosmetic().getAnimationFileLocation();
|
||||
AnimationFile animation = CosmeticsManager.getAnimations(location);
|
||||
if (animation == null) {
|
||||
animation = GeckoLibCache.getInstance().getAnimations().get(location);
|
||||
}
|
||||
|
||||
if (animation == null) {
|
||||
throw new GeckoLibException(location,
|
||||
"Could not find animation file. Please double check name.");
|
||||
}
|
||||
|
||||
return animation.getAnimation(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoModel getModel(Identifier location) {
|
||||
GeoModel model = CosmeticsManager.getModel(location);
|
||||
if (model == null) {
|
||||
model = GeckoLibCache.getInstance().getGeoModels().get(location);
|
||||
}
|
||||
|
||||
AnimatedGeoModelAccessor accessor = (AnimatedGeoModelAccessor)this;
|
||||
if (model != accessor.getCurrentModel()) {
|
||||
accessor.getAnimationProcessor().clearModelRendererList();
|
||||
accessor.setCurrentModel(model);
|
||||
|
||||
for (GeoBone bone : model.topLevelBones) {
|
||||
registerBone(bone);
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package net.anvilcraft.anvillib.cosmetics;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import software.bernie.geckolib3.geo.render.built.GeoBone;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoModel;
|
||||
|
||||
public class CosmeticParts {
|
||||
public boolean head = false;
|
||||
public boolean body = false;
|
||||
public boolean leftArm = false;
|
||||
public boolean leftLeg = false;
|
||||
public boolean rightArm = false;
|
||||
public boolean rightLeg = false;
|
||||
|
||||
public final String headName = "head";
|
||||
public final String bodyName = "body";
|
||||
public final String leftArmName = "arm_left";
|
||||
public final String leftLegName = "leg_left";
|
||||
public final String rightArmName = "arm_right";
|
||||
public final String rightLegName = "leg_right";
|
||||
|
||||
public CosmeticParts() {
|
||||
|
||||
}
|
||||
|
||||
public CosmeticParts(GeoModel model) {
|
||||
Optional<GeoBone> maybeRoot = model.getBone("root");
|
||||
if (maybeRoot.isEmpty()) return;
|
||||
GeoBone root = maybeRoot.get();
|
||||
for (GeoBone bone : root.childBones) {
|
||||
switch (bone.name) {
|
||||
case headName:
|
||||
this.head = true;
|
||||
break;
|
||||
case bodyName:
|
||||
this.body = true;
|
||||
break;
|
||||
case leftArmName:
|
||||
this.leftArm = true;
|
||||
break;
|
||||
case leftLegName:
|
||||
this.leftLeg = true;
|
||||
break;
|
||||
case rightArmName:
|
||||
this.rightArm = true;
|
||||
break;
|
||||
case rightLegName:
|
||||
this.rightLeg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,11 +7,18 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import software.bernie.geckolib3.file.AnimationFile;
|
||||
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 Set<UUID> activePlayers = new HashSet<>();
|
||||
private static Map<Identifier, GeoModel> cachedModels = new ConcurrentHashMap<>();
|
||||
private static Map<Identifier, AnimationFile> cachedAnimations = new ConcurrentHashMap<>();
|
||||
|
||||
private static void refresh() {
|
||||
boolean doRefresh = false;
|
||||
|
@ -48,4 +55,21 @@ public class CosmeticsManager {
|
|||
refresh();
|
||||
return cosmeticCache.get(uuid);
|
||||
}
|
||||
|
||||
protected static GeoModel getModel(Identifier id) {
|
||||
return cachedModels.get(id);
|
||||
}
|
||||
|
||||
protected static AnimationFile getAnimations(Identifier id) {
|
||||
return cachedAnimations.get(id);
|
||||
}
|
||||
|
||||
public static void loadModel(Identifier id, GeoModel model) {
|
||||
cachedModels.put(id, model);
|
||||
}
|
||||
|
||||
public static void loadAnimations(Identifier id, AnimationFile animations) {
|
||||
cachedAnimations.put(id, animations);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote;
|
||||
|
||||
import net.anvilcraft.anvillib.cosmetics.CosmeticsManager;
|
||||
import net.anvilcraft.anvillib.cosmetics.ICosmetic;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.AnimationData;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.TextureData;
|
||||
import net.anvilcraft.anvillib.cosmetics.CosmeticParts;
|
||||
import net.minecraft.util.Identifier;
|
||||
import software.bernie.geckolib3.file.AnimationFile;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoModel;
|
||||
|
||||
public class RemoteCosmetic implements ICosmetic {
|
||||
|
||||
private Identifier id;
|
||||
private boolean loadedModel = false;
|
||||
private boolean loadedTexture = false;
|
||||
private boolean loadedAnimations = false;
|
||||
private Identifier modelLocation;
|
||||
private Identifier textureLocation;
|
||||
private Identifier animationsLocation;
|
||||
private CosmeticParts parts = new CosmeticParts();
|
||||
private String idleAnimation = null;
|
||||
private int frameCount = 1;
|
||||
|
||||
public RemoteCosmetic(String id) {
|
||||
this.id = new Identifier("anvillib", id);
|
||||
this.modelLocation = new Identifier("anvillib", "models/remote/"+id);
|
||||
this.textureLocation = new Identifier("anvillib", "textures/remote/"+id);
|
||||
this.animationsLocation = new Identifier("anvillib", "animations/remote/"+id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getAnimationFileLocation() {
|
||||
return this.animationsLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getModelLocation() {
|
||||
return this.modelLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getTextureLocation() {
|
||||
return this.textureLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getID() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readyToRender() {
|
||||
return this.loadedModel && this.loadedTexture && this.loadedAnimations;
|
||||
}
|
||||
|
||||
public void loadModel(GeoModel model) {
|
||||
CosmeticsManager.loadModel(this.modelLocation, model);
|
||||
this.parts = new CosmeticParts(model);
|
||||
this.loadedModel = true;
|
||||
}
|
||||
|
||||
public void loadTexture(TextureData data) {
|
||||
this.frameCount = data.frameCount;
|
||||
this.loadedTexture = true;
|
||||
}
|
||||
|
||||
public void loadAnimations(AnimationFile file, AnimationData data) {
|
||||
if (data == null || file == null) {
|
||||
this.animationsLocation = null;
|
||||
} else {
|
||||
CosmeticsManager.loadAnimations(this.animationsLocation, file);
|
||||
this.idleAnimation = data.idleAnimation;
|
||||
}
|
||||
this.loadedAnimations = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return this.parts.body ? this.parts.bodyName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHead() {
|
||||
return this.parts.head ? this.parts.headName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLeftArm() {
|
||||
return this.parts.leftArm ? this.parts.leftArmName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLeftLeg() {
|
||||
return this.parts.leftLeg ? this.parts.leftLegName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRightArm() {
|
||||
return this.parts.rightArm ? this.parts.rightArmName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRightLeg() {
|
||||
return this.parts.rightLeg ? this.parts.rightLegName : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdleAnimationName() {
|
||||
return this.idleAnimation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalFrames() {
|
||||
return this.frameCount;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.CosmeticAssetsLoaderThread;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.thread.CosmeticLoaderThread;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.thread.PlayerCosmeticLoaderThread;
|
||||
import net.minecraft.util.Util;
|
||||
|
||||
public class RemoteCosmeticProvider implements ICosmeticProvider {
|
||||
|
||||
public static RemoteCosmeticProvider INSTANCE = null;
|
||||
|
||||
public final Map<String, RemoteCosmetic> cosmetics = new ConcurrentHashMap<>();
|
||||
public final Map<UUID, Set<String>> playerCosmetics = new ConcurrentHashMap<>();
|
||||
private final Map<String, Boolean> knownCosmetics = new ConcurrentHashMap<>();
|
||||
private boolean dirty = false;
|
||||
|
||||
public final URI playerBase;
|
||||
public final URI cosmeticBase;
|
||||
private final File cacheDir;
|
||||
|
||||
public RemoteCosmeticProvider(URI playerBase, URI cosmeticBase, File cacheDir) {
|
||||
this.playerBase = playerBase;
|
||||
this.cosmeticBase = cosmeticBase;
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestsRefresh() {
|
||||
return this.dirty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCosmetics(UUID player, Consumer<ICosmetic> cosmeticAdder) {
|
||||
this.dirty = false;
|
||||
if (playerCosmetics.containsKey(player)) {
|
||||
for (String id : playerCosmetics.get(player)) {
|
||||
if (!this.cosmetics.containsKey(id) || !this.cosmetics.get(id).readyToRender()) continue;
|
||||
cosmeticAdder.accept(this.cosmetics.get(id));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.loadNewPlayer(player);
|
||||
} catch (MalformedURLException e) {
|
||||
AnvilLib.LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
synchronized(this) {
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNewPlayer(UUID id) throws MalformedURLException {
|
||||
this.playerCosmetics.putIfAbsent(id, new HashSet<>());
|
||||
URI url = playerBase.resolve(id.toString());
|
||||
Util.getMainWorkerExecutor().execute(new PlayerCosmeticLoaderThread(url, this));
|
||||
}
|
||||
|
||||
public void loadCosmetic(String id) throws MalformedURLException {
|
||||
if (this.cosmetics.containsKey(id) || knownCosmetics.containsKey(id)) return;
|
||||
this.knownCosmetics.put(id, true);
|
||||
URI url = cosmeticBase.resolve(id);
|
||||
Util.getMainWorkerExecutor().execute(new CosmeticLoaderThread(url, this));
|
||||
}
|
||||
|
||||
public void loadAssets(CosmeticData data, RemoteCosmetic cosmetic) {
|
||||
Util.getMainWorkerExecutor().execute(new CosmeticAssetsLoaderThread(cosmetic, data, this.cacheDir, this));
|
||||
}
|
||||
|
||||
public void failCosmeticLoading(String id) {
|
||||
AnvilLib.LOGGER.error("Cosmetic loading failed: {}", id);
|
||||
this.cosmetics.remove(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class AnimationData {
|
||||
|
||||
@Expose
|
||||
public String url;
|
||||
@Expose
|
||||
@SerializedName("idle_animation")
|
||||
public String idleAnimation;
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class CosmeticData {
|
||||
|
||||
@Expose
|
||||
public String id;
|
||||
@Expose
|
||||
@SerializedName("model_url")
|
||||
public String modelUrl;
|
||||
@Expose
|
||||
@SerializedName("animation_data")
|
||||
public AnimationData animationData;
|
||||
@Expose
|
||||
@SerializedName("texture_data")
|
||||
public TextureData textureData;
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
public class PlayerData {
|
||||
|
||||
@Expose
|
||||
public UUID uuid;
|
||||
@Expose
|
||||
public List<String> cosmetics;
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class TextureData {
|
||||
|
||||
@Expose
|
||||
public String url;
|
||||
@Expose
|
||||
@SerializedName("total_frames")
|
||||
public int frameCount;
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.thread;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.anvilcraft.anvillib.AnvilLib;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmetic;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmeticProvider;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.AnimationData;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.CosmeticData;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.TextureData;
|
||||
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;
|
||||
import software.bernie.geckolib3.core.builder.Animation;
|
||||
import software.bernie.geckolib3.core.molang.MolangParser;
|
||||
import software.bernie.geckolib3.file.AnimationFile;
|
||||
import software.bernie.geckolib3.geo.exception.GeckoLibException;
|
||||
import software.bernie.geckolib3.geo.raw.pojo.Converter;
|
||||
import software.bernie.geckolib3.geo.raw.pojo.FormatVersion;
|
||||
import software.bernie.geckolib3.geo.raw.pojo.RawGeoModel;
|
||||
import software.bernie.geckolib3.geo.raw.tree.RawGeometryTree;
|
||||
import software.bernie.geckolib3.geo.render.GeoBuilder;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoModel;
|
||||
import software.bernie.geckolib3.util.json.JsonAnimationUtils;
|
||||
|
||||
public class CosmeticAssetsLoaderThread extends FileDownloaderThread {
|
||||
|
||||
private RemoteCosmetic cosmetic;
|
||||
private CosmeticData data;
|
||||
private MolangParser parser = new MolangParser();
|
||||
private TextureManager textureManager = MinecraftClient.getInstance().getTextureManager();
|
||||
private File cacheDir;
|
||||
private RemoteCosmeticProvider provider;
|
||||
|
||||
public CosmeticAssetsLoaderThread(RemoteCosmetic cosmetic, CosmeticData data, File cacheDir, RemoteCosmeticProvider provider) {
|
||||
super("0.2.0");
|
||||
this.cosmetic = cosmetic;
|
||||
this.data = data;
|
||||
this.cacheDir = cacheDir;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.loadModel(this.data.modelUrl);
|
||||
this.loadTexture(this.data.textureData);
|
||||
this.loadAnimations(this.data.animationData);
|
||||
this.provider.markDirty();
|
||||
}
|
||||
|
||||
private void loadAnimations(AnimationData anim) {
|
||||
if (anim == null) {
|
||||
this.cosmetic.loadAnimations(null, anim);
|
||||
return;
|
||||
}
|
||||
AnimationFile animations = null;
|
||||
try {
|
||||
URI url = new URI(this.data.animationData.url);
|
||||
JsonObject data = this.loadJson(url, JsonObject.class);
|
||||
animations = this.buildAnimationFile(data);
|
||||
} catch (IOException | URISyntaxException | NullPointerException e) {
|
||||
AnvilLib.LOGGER.error("Could not load animation: {}", this.data.animationData.url, e);
|
||||
}
|
||||
this.cosmetic.loadAnimations(animations, anim);
|
||||
}
|
||||
|
||||
private void loadTexture(TextureData data) {
|
||||
String hash = Hashing.sha1().hashUnencodedChars(this.data.id).toString();
|
||||
AbstractTexture texture = this.textureManager.getOrDefault(this.cosmetic.getTextureLocation(), 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(this.cosmetic.getTextureLocation(), texture);
|
||||
}
|
||||
this.cosmetic.loadTexture(data);
|
||||
}
|
||||
|
||||
private void loadModel(String url) {
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
String data = Objects.requireNonNull(this.getStringForURL(uri));
|
||||
GeoModel model = this.buildModel(data);
|
||||
this.cosmetic.loadModel(model);
|
||||
} catch (NullPointerException | URISyntaxException | IOException | GeckoLibException e) {
|
||||
AnvilLib.LOGGER.error("Can't load remote model: {}", url, e);
|
||||
this.handleFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFailure() {
|
||||
this.provider.failCosmeticLoading(this.data.id);
|
||||
}
|
||||
|
||||
private AnimationFile buildAnimationFile(JsonObject json) {
|
||||
AnimationFile animationFile = new AnimationFile();
|
||||
for (Map.Entry<String, JsonElement> entry : JsonAnimationUtils.getAnimations(json)) {
|
||||
String animationName = entry.getKey();
|
||||
Animation animation;
|
||||
try {
|
||||
animation = JsonAnimationUtils.deserializeJsonToAnimation(
|
||||
JsonAnimationUtils.getAnimation(json, animationName), parser);
|
||||
animationFile.putAnimation(animationName, animation);
|
||||
} catch (Exception e) {
|
||||
AnvilLib.LOGGER.error("Could not load animation: {}", animationName, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return animationFile;
|
||||
}
|
||||
|
||||
private GeoModel buildModel(String json) throws IOException {
|
||||
Identifier location = this.cosmetic.getModelLocation();
|
||||
RawGeoModel rawModel = Converter.fromJsonString(json);
|
||||
if (rawModel.getFormatVersion() != FormatVersion.VERSION_1_12_0) {
|
||||
throw new GeckoLibException(location, "Wrong geometry json version, expected 1.12.0");
|
||||
}
|
||||
RawGeometryTree rawGeometryTree = RawGeometryTree.parseHierarchy(rawModel);
|
||||
return GeoBuilder.getGeoBuilder(location.getNamespace()).constructGeoModel(rawGeometryTree);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.thread;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import net.anvilcraft.anvillib.AnvilLib;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmetic;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmeticProvider;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.CosmeticData;
|
||||
|
||||
public class CosmeticLoaderThread extends FileDownloaderThread {
|
||||
|
||||
private URI url;
|
||||
private RemoteCosmeticProvider provider;
|
||||
|
||||
public CosmeticLoaderThread(URI url, RemoteCosmeticProvider provider) {
|
||||
super("0.2.0");
|
||||
this.url = url;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
CosmeticData data = this.loadJson(url, CosmeticData.class);
|
||||
if (data == null) throw new IOException("Cosmetic not found");
|
||||
RemoteCosmetic cosmetic = new RemoteCosmetic(data.id);
|
||||
this.provider.cosmetics.put(data.id, cosmetic);
|
||||
this.provider.loadAssets(data, cosmetic);
|
||||
} catch (IOException e) {
|
||||
AnvilLib.LOGGER.error("Can't load cosmetic", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.thread;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import net.anvilcraft.anvillib.AnvilLib;
|
||||
|
||||
public abstract class FileDownloaderThread implements Runnable {
|
||||
|
||||
protected Gson gson = new GsonBuilder().create();
|
||||
protected HttpClient client = HttpClient.newBuilder().build();
|
||||
protected final String version;
|
||||
|
||||
public FileDownloaderThread(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
protected HttpRequest buildRequest(URI url) {
|
||||
return HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(url)
|
||||
.header("User-Agent", System.getProperty("java.version"))
|
||||
.header("X-AnvilLib-Version", this.version)
|
||||
.header("X-Minecraft-Version", "1.18.2")
|
||||
.build();
|
||||
}
|
||||
|
||||
public InputStream getStreamForURL(URI url) {
|
||||
HttpRequest req = this.buildRequest(url);
|
||||
InputStream is = null;
|
||||
try {
|
||||
HttpResponse<InputStream> res = client.send(req, HttpResponse.BodyHandlers.ofInputStream());
|
||||
if (res.statusCode() == 200) {
|
||||
is = res.body();
|
||||
} else if (res.statusCode() != 404) {
|
||||
AnvilLib.LOGGER.error("Unexpected status code: {}", res.statusCode());
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
AnvilLib.LOGGER.error(e);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
public String getStringForURL(URI url) {
|
||||
HttpRequest req = this.buildRequest(url);
|
||||
String is = null;
|
||||
try {
|
||||
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
if (res.statusCode() == 200) {
|
||||
is = res.body();
|
||||
} else if (res.statusCode() != 404) {
|
||||
AnvilLib.LOGGER.error("Unexpected status code: {}", res.statusCode());
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
AnvilLib.LOGGER.error(e);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
public <T> T loadJson(URI url, Class<T> type) throws IOException {
|
||||
InputStream stream = this.getStreamForURL(url);
|
||||
if (stream == null) return null;
|
||||
try {
|
||||
T json = this.gson.fromJson(new InputStreamReader(stream), type);
|
||||
return json;
|
||||
} catch(JsonSyntaxException | JsonIOException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package net.anvilcraft.anvillib.cosmetics.remote.thread;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.RemoteCosmeticProvider;
|
||||
import net.anvilcraft.anvillib.cosmetics.remote.model.PlayerData;
|
||||
|
||||
public class PlayerCosmeticLoaderThread extends FileDownloaderThread{
|
||||
|
||||
private URI config;
|
||||
private RemoteCosmeticProvider provider;
|
||||
|
||||
public PlayerCosmeticLoaderThread(URI config, RemoteCosmeticProvider provider) {
|
||||
super("0.2.0");
|
||||
this.config = config;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PlayerData player = this.loadJson(config, PlayerData.class);
|
||||
if (player == null) return;
|
||||
for (String id : player.cosmetics) {
|
||||
this.provider.loadCosmetic(id);
|
||||
this.provider.playerCosmetics.get(player.uuid).add(id);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package net.anvilcraft.anvillib.mixin.accessor;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import software.bernie.geckolib3.core.processor.AnimationProcessor;
|
||||
import software.bernie.geckolib3.geo.render.built.GeoModel;
|
||||
import software.bernie.geckolib3.model.AnimatedGeoModel;
|
||||
|
||||
@Mixin(AnimatedGeoModel.class)
|
||||
public interface AnimatedGeoModelAccessor {
|
||||
|
||||
@Accessor(remap = false)
|
||||
AnimationProcessor getAnimationProcessor();
|
||||
|
||||
@Accessor(remap = false)
|
||||
GeoModel getCurrentModel();
|
||||
|
||||
@Accessor(remap = false)
|
||||
void setCurrentModel(GeoModel model);
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"minVersion": "0.8",
|
||||
"client": [],
|
||||
"mixins": [
|
||||
"accessor.AnimatedGeoModelAccessor",
|
||||
"common.RecipeManagerMixin",
|
||||
"common.StructurePoolBasedGeneratorMixin"
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue