diff --git a/gradle.properties b/gradle.properties index 381585c..7645903 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,5 +12,5 @@ halplibe_version=3.5.4 # Mod mod_version=1.0.0 -mod_group=turniplabs -mod_name=examplemod +mod_group=alterdekim +mod_name=offlineskin diff --git a/src/main/java/alterwain/offlineskin/ForceDownloadHandler.java b/src/main/java/alterwain/offlineskin/ForceDownloadHandler.java new file mode 100644 index 0000000..902c3cd --- /dev/null +++ b/src/main/java/alterwain/offlineskin/ForceDownloadHandler.java @@ -0,0 +1,7 @@ +package alterwain.offlineskin; + +import net.minecraft.client.render.ImageParser; + +public interface ForceDownloadHandler { + boolean offlineSkinChanger$forceLoadDownloadableTexture(String url, String localTexture, ImageParser imageParser); +} diff --git a/src/main/java/alterwain/offlineskin/GuiSkinChanger.java b/src/main/java/alterwain/offlineskin/GuiSkinChanger.java new file mode 100644 index 0000000..75e7128 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/GuiSkinChanger.java @@ -0,0 +1,67 @@ +package alterwain.offlineskin; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.core.lang.I18n; + +import javax.imageio.ImageIO; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +import static alterwain.offlineskin.OfflineSkinMod.configPath; + +public class GuiSkinChanger extends GuiScreen { + + private GuiButton buttonLoadSkin; + private GuiButton buttonLoadCape; + private GuiButton buttonClose; + + + public GuiSkinChanger(GuiScreen parent) { + super(parent); + } + + @Override + public void init() { + I18n stringtranslate = I18n.getInstance(); + this.controlList.clear(); + this.controlList.add(this.buttonLoadSkin = new GuiButton(0, this.width / 2 - 100, this.height / 4 + 96 + 12, stringtranslate.translateKey("gui.options.page.edit_skin.button.load_skin"))); + this.controlList.add(this.buttonLoadCape = new GuiButton(1, this.width / 2 - 100, this.height / 4 - 10 + 50 + 18 + 20 + 4, 200, 20, stringtranslate.translateKey("gui.options.page.edit_skin.button.load_cape"))); + this.controlList.add(this.buttonClose = new GuiButton(2, this.width / 2 - 100, this.height / 4 + 120 + 12, stringtranslate.translateKey("gui.options.page.edit_skin.button.close"))); + } + + @Override + protected void buttonPressed(GuiButton button) { + try { + if (button.enabled) { + if (button.id == 2) { + this.mc.displayGuiScreen(this.getParentScreen()); + } else if (button.id == 0) { // skin + File skin = OfflineSkinMod.chooseFile(); + if (skin != null) { + OfflineSkinMod.skinImage = ImageIO.read(skin); + Files.copy(skin.toPath(), new File(configPath, "skin.png").toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } else if (button.id == 1) { // cape + File cape = OfflineSkinMod.chooseFile(); + if (cape != null) { + OfflineSkinMod.capeImage = ImageIO.read(cape); + Files.copy(cape.toPath(), new File(configPath, "cape.png").toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTick) { + I18n stringtranslate = I18n.getInstance(); + this.drawDefaultBackground(); + this.drawStringCentered(this.fontRenderer, stringtranslate.translateKey("gui.options.page.edit_skin.label.title"), this.width / 2, this.height / 4 - 60 + 20, 16777215); + super.drawScreen(mouseX, mouseY, partialTick); + } +} diff --git a/src/main/java/alterwain/offlineskin/OfflineSkinMod.java b/src/main/java/alterwain/offlineskin/OfflineSkinMod.java new file mode 100644 index 0000000..0216f97 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/OfflineSkinMod.java @@ -0,0 +1,97 @@ +package alterwain.offlineskin; + +import net.fabricmc.api.ModInitializer; +import net.minecraft.core.Global; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import turniplabs.halplibe.util.GameStartEntrypoint; +import turniplabs.halplibe.util.RecipeEntrypoint; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + + +public class OfflineSkinMod implements ModInitializer, GameStartEntrypoint, RecipeEntrypoint { + public static final String MOD_ID = "offlineskin"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + public static File configPath = new File(Global.accessor.getMinecraftDir(), "config/offlineskin"); + public static BufferedImage skinImage; + public static BufferedImage capeImage; + public static final Map skins = new HashMap<>(); + + @Override + public void onInitialize() { + LOGGER.info("ExampleMod initialized."); + } + + @Override + public void beforeGameStart() { + configPath.mkdirs(); + try { + if (new File(configPath, "skin.png").exists()) { + skinImage = ImageIO.read(new File(configPath, "skin.png")); + } + if (new File(configPath, "cape.png").exists()) { + capeImage = ImageIO.read(new File(configPath, "cape.png")); + } + } catch (IOException e) { + LOGGER.error(e.getMessage()); + } + } + + @Override + public void afterGameStart() { + + } + + @Override + public void onRecipesReady() { + + } + + @Override + public void initNamespaces() { + + } + + public static File chooseFile() { + JFileChooser fileChooser = new JFileChooser(); + FileFilter filter = new FileNameExtensionFilter("PNG File","png"); + fileChooser.addChoosableFileFilter(filter); + int returnValue = fileChooser.showOpenDialog(null); + if (returnValue == JFileChooser.APPROVE_OPTION) { + return fileChooser.getSelectedFile(); + } + return null; + } + + public static BufferedImage bytesToImage(byte[] b) { + try { + ByteArrayInputStream is = new ByteArrayInputStream(b); + return ImageIO.read(is); + } catch (IOException e) { + return null; // TODO: read default skin from resources. + } + } + + public static byte[] imageToBytes(BufferedImage image) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "png", baos); + } catch (IOException e) { + return new byte[1]; + } + return baos.toByteArray(); + } +} diff --git a/src/main/java/alterwain/offlineskin/SendInfo.java b/src/main/java/alterwain/offlineskin/SendInfo.java new file mode 100644 index 0000000..2d9a589 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/SendInfo.java @@ -0,0 +1,28 @@ +package alterwain.offlineskin; + +import alterwain.offlineskin.packet.Packet246SkinSet; +import net.minecraft.server.entity.player.EntityPlayerMP; + +public class SendInfo extends Thread { + + private EntityPlayerMP player; + + public SendInfo(EntityPlayerMP player) { + this.player = player; + } + + @Override + public void run() { + try { + Thread.sleep(3000); + OfflineSkinMod.skins.keySet().stream().filter(k -> !k.equals(player.username)) + .forEach(k -> player.playerNetServerHandler.sendPacket(new Packet246SkinSet(k, + OfflineSkinMod.imageToBytes(OfflineSkinMod.skins.get(k).getSkin()), + OfflineSkinMod.imageToBytes(OfflineSkinMod.skins.get(k).getCape()), + OfflineSkinMod.skins.get(k).getModelType() + ))); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/alterwain/offlineskin/SendSet.java b/src/main/java/alterwain/offlineskin/SendSet.java new file mode 100644 index 0000000..b214e7b --- /dev/null +++ b/src/main/java/alterwain/offlineskin/SendSet.java @@ -0,0 +1,29 @@ +package alterwain.offlineskin; + +import net.minecraft.client.render.EntityRenderDispatcher; +import net.minecraft.client.render.PlayerSkinParser; + +public class SendSet extends Thread { + + private final String username; + private final boolean isCape; + + public SendSet(String username, boolean isCape) { + this.username = username; + this.isCape = isCape; + } + + @Override + public void run() { + try { + Thread.sleep(2500); + if( isCape ) { + ((ForceDownloadHandler) EntityRenderDispatcher.instance.renderEngine).offlineSkinChanger$forceLoadDownloadableTexture("offlineCapeLocal:" + this.username, null, null); + return; + } + ((ForceDownloadHandler) EntityRenderDispatcher.instance.renderEngine).offlineSkinChanger$forceLoadDownloadableTexture("offlineSkinLocal:"+this.username, null, PlayerSkinParser.instance); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/alterwain/offlineskin/SkinConfig.java b/src/main/java/alterwain/offlineskin/SkinConfig.java new file mode 100644 index 0000000..c4ee848 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/SkinConfig.java @@ -0,0 +1,27 @@ +package alterwain.offlineskin; + +import java.awt.image.BufferedImage; + +public class SkinConfig { + private BufferedImage skin; + private BufferedImage cape; + private Boolean modelType; + + public SkinConfig(BufferedImage skin, BufferedImage cape, Boolean modelType) { + this.skin = skin; + this.cape = cape; + this.modelType = modelType; + } + + public BufferedImage getSkin() { + return skin; + } + + public BufferedImage getCape() { + return cape; + } + + public Boolean getModelType() { + return modelType; + } +} diff --git a/src/main/java/alterwain/offlineskin/SkinRequestHandler.java b/src/main/java/alterwain/offlineskin/SkinRequestHandler.java new file mode 100644 index 0000000..33e6329 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/SkinRequestHandler.java @@ -0,0 +1,8 @@ +package alterwain.offlineskin; + + +import alterwain.offlineskin.packet.Packet244SkinRequest; + +public interface SkinRequestHandler { + void offlineSkinChanger$handleSkinRequest(Packet244SkinRequest request); +} diff --git a/src/main/java/alterwain/offlineskin/SkinResponseHandler.java b/src/main/java/alterwain/offlineskin/SkinResponseHandler.java new file mode 100644 index 0000000..0f50954 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/SkinResponseHandler.java @@ -0,0 +1,7 @@ +package alterwain.offlineskin; + +import alterwain.offlineskin.packet.Packet245SkinResponse; + +public interface SkinResponseHandler { + void offlineSkinChanger$handleSkinResponse(Packet245SkinResponse response); +} diff --git a/src/main/java/alterwain/offlineskin/mixin/NetClientHandlerMixin.java b/src/main/java/alterwain/offlineskin/mixin/NetClientHandlerMixin.java new file mode 100644 index 0000000..617f5ac --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/NetClientHandlerMixin.java @@ -0,0 +1,28 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.OfflineSkinMod; +import alterwain.offlineskin.packet.Packet244SkinRequest; +import alterwain.offlineskin.packet.Packet245SkinResponse; +import alterwain.offlineskin.SkinRequestHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.net.handler.NetClientHandler; +import net.minecraft.core.net.NetworkManager; +import net.minecraft.core.net.handler.NetHandler; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(NetClientHandler.class) +public abstract class NetClientHandlerMixin extends NetHandler implements SkinRequestHandler { + @Shadow @Final private NetworkManager netManager; + + @Shadow @Final private Minecraft mc; + + @Override + public void offlineSkinChanger$handleSkinRequest(Packet244SkinRequest request) { + this.netManager.addToSendQueue(new Packet245SkinResponse(mc.thePlayer.username, + OfflineSkinMod.imageToBytes(OfflineSkinMod.skinImage), + OfflineSkinMod.imageToBytes(OfflineSkinMod.capeImage), + false)); + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/NetServerHandlerMixin.java b/src/main/java/alterwain/offlineskin/mixin/NetServerHandlerMixin.java new file mode 100644 index 0000000..37b15ed --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/NetServerHandlerMixin.java @@ -0,0 +1,32 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.OfflineSkinMod; +import alterwain.offlineskin.SkinConfig; +import alterwain.offlineskin.SkinResponseHandler; +import alterwain.offlineskin.packet.Packet245SkinResponse; +import alterwain.offlineskin.packet.Packet246SkinSet; +import net.minecraft.server.net.handler.NetServerHandler; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(NetServerHandler.class) +public abstract class NetServerHandlerMixin extends net.minecraft.core.net.handler.NetHandler implements net.minecraft.core.net.ICommandListener, + SkinResponseHandler { + @Shadow + private net.minecraft.server.MinecraftServer mcServer; + + @Override + public void offlineSkinChanger$handleSkinResponse(Packet245SkinResponse response) { + OfflineSkinMod.skins.put(response.getUsername(), new SkinConfig( + OfflineSkinMod.bytesToImage(response.getSkin()), + OfflineSkinMod.bytesToImage(response.getCape()), + response.isModelType() + )); + for( int i = 0; i < this.mcServer.playerList.playerEntities.size(); i++ ) { + this.mcServer.playerList.playerEntities.get(i) + .playerNetServerHandler + .sendPacket(new Packet246SkinSet(response.getUsername(), response.getSkin(), response.getCape(), response.isModelType())); + } + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/OptionsPagesMixin.java b/src/main/java/alterwain/offlineskin/mixin/OptionsPagesMixin.java new file mode 100644 index 0000000..bc31407 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/OptionsPagesMixin.java @@ -0,0 +1,28 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.GuiSkinChanger; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.options.components.*; +import net.minecraft.client.gui.options.data.OptionsPages; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(OptionsPages.class) +public abstract class OptionsPagesMixin { + @Final + @Shadow + @Mutable + private static Minecraft mc; + + @Inject(method = "", at = @At("TAIL")) + private static void modifyGeneralScreen(CallbackInfo ci) { + OptionsPages.GENERAL.withComponent(new ShortcutComponent("gui.options.page.general.button.edit_skin", () -> { + mc.displayGuiScreen(new GuiSkinChanger(mc.currentScreen)); + })); + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/PacketMixin.java b/src/main/java/alterwain/offlineskin/mixin/PacketMixin.java new file mode 100644 index 0000000..aad69c8 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/PacketMixin.java @@ -0,0 +1,21 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.packet.Packet244SkinRequest; +import alterwain.offlineskin.packet.Packet245SkinResponse; +import alterwain.offlineskin.packet.Packet246SkinSet; +import net.minecraft.core.net.packet.Packet; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + + +@Mixin(Packet.class) +public abstract class PacketMixin { + @Inject(method = "", at = @At("TAIL")) + private static void modifyPacketsTable(CallbackInfo ci) { + Packet.addIdClassMapping(244, true, false, Packet244SkinRequest.class); + Packet.addIdClassMapping(245, false, true, Packet245SkinResponse.class); + Packet.addIdClassMapping(246, true, false, Packet246SkinSet.class); + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/PlayerManagerMixin.java b/src/main/java/alterwain/offlineskin/mixin/PlayerManagerMixin.java new file mode 100644 index 0000000..746366b --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/PlayerManagerMixin.java @@ -0,0 +1,30 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.OfflineSkinMod; +import alterwain.offlineskin.SendInfo; +import alterwain.offlineskin.packet.Packet244SkinRequest; +import alterwain.offlineskin.packet.Packet246SkinSet; +import net.minecraft.server.entity.player.EntityPlayerMP; +import net.minecraft.server.player.PlayerManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(PlayerManager.class) +public abstract class PlayerManagerMixin { + + @Shadow(remap = false) + @Final + public List players; + + @Inject(method = "addPlayer", at = @At("HEAD"), remap = false) + private void onAddPlayer(EntityPlayerMP player, CallbackInfo ci) { + player.playerNetServerHandler.sendPacket(new Packet244SkinRequest(true, true, false)); + new SendInfo(player).start(); + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/PlayerRendererMixin.java b/src/main/java/alterwain/offlineskin/mixin/PlayerRendererMixin.java new file mode 100644 index 0000000..e7bf10f --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/PlayerRendererMixin.java @@ -0,0 +1,168 @@ +package alterwain.offlineskin.mixin; + +import net.minecraft.client.render.ImageParser; +import net.minecraft.client.render.PlayerSkinParser; +import net.minecraft.client.render.block.model.BlockModel; +import net.minecraft.client.render.block.model.BlockModelDispatcher; +import net.minecraft.client.render.entity.LivingRenderer; +import net.minecraft.client.render.entity.PlayerRenderer; +import net.minecraft.client.render.model.ModelBase; +import net.minecraft.client.render.model.ModelBiped; +import net.minecraft.client.render.model.ModelPlayer; +import net.minecraft.core.block.Block; +import net.minecraft.core.entity.player.EntityPlayer; +import net.minecraft.core.item.Item; +import net.minecraft.core.item.ItemStack; +import net.minecraft.core.util.helper.MathHelper; +import org.lwjgl.opengl.GL11; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(PlayerRenderer.class) +public abstract class PlayerRendererMixin extends LivingRenderer { + + @Shadow(remap = false) + private ModelBiped modelBipedMain; + + @Final + @Shadow(remap = false) + private ModelPlayer modelSlim; + + @Final + @Shadow(remap = false) + private ModelPlayer modelThick; + + + public PlayerRendererMixin(ModelBase model, float shadowSize) { + super(model, shadowSize); + } + + @Override + public void loadEntityTexture(EntityPlayer entity) { + this.loadDownloadableTexture("offlineSkinLocal:"+entity.username, entity.getEntityTexture(), PlayerSkinParser.instance); + } + + public void drawFirstPersonHand(EntityPlayer player) { + player.skinURL = "offlineSkinLocal:"+player.username; + this.mainModel = player.slimModel ? this.modelSlim : this.modelThick; + this.modelBipedMain = player.slimModel ? this.modelSlim : this.modelThick; + this.modelBipedMain.onGround = 0.0F; + this.modelBipedMain.isRiding = false; + this.modelBipedMain.setRotationAngles(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0625F); + this.modelBipedMain.bipedRightArm.render(0.0625F); + if (this.modelBipedMain instanceof ModelPlayer) { + ((ModelPlayer)this.modelBipedMain).bipedRightArmOverlay.render(0.0625F); + } + } + + protected void renderSpecials(EntityPlayer entity, float f) { + ItemStack itemstack = entity.inventory.armorItemInSlot(3); + if (itemstack != null && itemstack.getItem().id < Block.blocksList.length) { + GL11.glPushMatrix(); + this.modelBipedMain.bipedHead.postRender(0.0625F); + if (((BlockModel) BlockModelDispatcher.getInstance().getDispatch(Block.blocksList[itemstack.itemID])).shouldItemRender3d()) { + float f1 = 0.625F; + GL11.glTranslatef(0.0F, -0.25F, 0.0F); + GL11.glRotatef(180.0F, 0.0F, 1.0F, 0.0F); + GL11.glScalef(f1, -f1, f1); + } + + this.renderDispatcher.itemRenderer.renderItem(entity, itemstack); + GL11.glPopMatrix(); + } + + boolean renderCape = this.loadDownloadableTexture("offlineCapeLocal:"+entity.username, (String)null, (ImageParser)null); + + if (renderCape) { + GL11.glPushMatrix(); + GL11.glTranslatef(0.0F, 0.0F, 0.125F); + double d = entity.field_20066_r + (entity.field_20063_u - entity.field_20066_r) * (double)f - (entity.xo + (entity.x - entity.xo) * (double)f); + double d1 = entity.field_20065_s + (entity.field_20062_v - entity.field_20065_s) * (double)f - (entity.yo + (entity.y - entity.yo) * (double)f); + double d2 = entity.field_20064_t + (entity.field_20061_w - entity.field_20064_t) * (double)f - (entity.zo + (entity.z - entity.zo) * (double)f); + float f8 = entity.prevRenderYawOffset + (entity.renderYawOffset - entity.prevRenderYawOffset) * f; + double d3 = (double) MathHelper.sin(f8 * 3.141593F / 180.0F); + double d4 = (double)(-MathHelper.cos(f8 * 3.141593F / 180.0F)); + float f9 = (float)d1 * 10.0F; + if (f9 < -6.0F) { + f9 = -6.0F; + } + + if (f9 > 32.0F) { + f9 = 32.0F; + } + + float f10 = (float)(d * d3 + d2 * d4) * 100.0F; + float f11 = (float)(d * d4 - d2 * d3) * 100.0F; + if (f10 < 0.0F) { + f10 = 0.0F; + } + + float f12 = entity.field_775_e + (entity.field_774_f - entity.field_775_e) * f; + f9 += MathHelper.sin((entity.walkDistO + (entity.walkDist - entity.walkDistO) * f) * 6.0F) * 32.0F * f12; + if (entity.isSneaking()) { + f9 += 25.0F; + } + + GL11.glRotatef(6.0F + f10 / 2.0F + f9, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(f11 / 2.0F, 0.0F, 0.0F, 1.0F); + GL11.glRotatef(-f11 / 2.0F, 0.0F, 1.0F, 0.0F); + GL11.glRotatef(180.0F, 0.0F, 1.0F, 0.0F); + this.modelBipedMain.renderCloak(0.0625F); + GL11.glPopMatrix(); + } + + ItemStack itemstack1 = entity.inventory.getCurrentItem(); + if (itemstack1 != null) { + GL11.glPushMatrix(); + this.modelBipedMain.bipedRightArm.postRender(0.0625F); + GL11.glTranslatef(-0.0625F, 0.4375F, 0.0625F); + if (entity.fishEntity != null) { + itemstack1 = new ItemStack(Item.stick); + } + + float f4; + if (itemstack1.itemID < Block.blocksList.length && ((BlockModel)BlockModelDispatcher.getInstance().getDispatch(Block.blocksList[itemstack1.itemID])).shouldItemRender3d()) { + f4 = 0.5F; + GL11.glTranslatef(0.0F, 0.1875F, -0.3125F); + f4 *= 0.75F; + GL11.glRotatef(20.0F, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(45.0F, 0.0F, 1.0F, 0.0F); + GL11.glScalef(f4, -f4, f4); + } else if (itemstack1.itemID == Item.toolBow.id) { + f4 = 0.625F; + GL11.glTranslatef(0.0F, 0.125F, 0.3125F); + GL11.glRotatef(-20.0F, 0.0F, 1.0F, 0.0F); + GL11.glScalef(f4, -f4, f4); + GL11.glRotatef(-100.0F, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(45.0F, 0.0F, 1.0F, 0.0F); + } else if (Item.itemsList[itemstack1.itemID].isFull3D()) { + f4 = 0.625F; + if (Item.itemsList[itemstack1.itemID].shouldRotateAroundWhenRendering()) { + GL11.glRotatef(180.0F, 0.0F, 0.0F, 1.0F); + GL11.glTranslatef(0.0F, -0.125F, 0.0F); + } + + if (Item.itemsList[itemstack1.itemID].shouldPointInFrontOfPlayer()) { + GL11.glRotatef(-20.0F, 0.0F, 1.0F, 0.0F); + GL11.glTranslatef(0.0F, -0.125F, 0.0F); + } + + GL11.glTranslatef(0.0F, 0.1875F, 0.0F); + GL11.glScalef(f4, -f4, f4); + GL11.glRotatef(-100.0F, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(45.0F, 0.0F, 1.0F, 0.0F); + } else { + f4 = 0.375F; + GL11.glTranslatef(0.25F, 0.1875F, -0.1875F); + GL11.glScalef(f4, f4, f4); + GL11.glRotatef(60.0F, 0.0F, 0.0F, 1.0F); + GL11.glRotatef(-90.0F, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(20.0F, 0.0F, 0.0F, 1.0F); + } + + this.renderDispatcher.itemRenderer.renderItem(entity, itemstack1); + GL11.glPopMatrix(); + } + } +} diff --git a/src/main/java/alterwain/offlineskin/mixin/RenderEngineMixin.java b/src/main/java/alterwain/offlineskin/mixin/RenderEngineMixin.java new file mode 100644 index 0000000..8471288 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/mixin/RenderEngineMixin.java @@ -0,0 +1,107 @@ +package alterwain.offlineskin.mixin; + +import alterwain.offlineskin.ForceDownloadHandler; +import alterwain.offlineskin.OfflineSkinMod; +import net.minecraft.client.render.DownloadedTexture; +import net.minecraft.client.render.ImageParser; +import net.minecraft.client.render.RenderEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.awt.image.BufferedImage; +import java.util.Map; + +@Mixin(RenderEngine.class) +public abstract class RenderEngineMixin implements ForceDownloadHandler { + + @Shadow + private Map downloadedTextures; + + @Shadow + public abstract int allocateAndSetupTexture(BufferedImage bufferedimage); + + @Shadow + public abstract void bindTexture(int i); + + @Shadow + public abstract int getTexture(String name); + + @Override + public boolean offlineSkinChanger$forceLoadDownloadableTexture(String url, String localTexture, ImageParser imageParser) { + DownloadedTexture texture = new DownloadedTexture(null, imageParser); + if( url.startsWith("offlineSkinLocal") ) { + if( OfflineSkinMod.skins.containsKey(url.substring(17)) ) { + texture.image = OfflineSkinMod.skins.get(url.substring(17)).getSkin(); + } else { + texture.image = OfflineSkinMod.skinImage; + } + } else if( url.startsWith("offlineCapeLocal") ) { + if( OfflineSkinMod.skins.containsKey(url.substring(17))) { + texture.image = OfflineSkinMod.skins.get(url.substring(17)).getCape(); + } else { + texture.image = OfflineSkinMod.capeImage; + } + } else { + texture = new DownloadedTexture(url, imageParser); + } + this.downloadedTextures.put(url, texture); + + + if (texture.textureId < 0 && texture.image != null) { + texture.textureId = this.allocateAndSetupTexture(texture.image); + } + + if (texture.textureId > 0) { + this.bindTexture(texture.textureId); + return true; + } else { + return false; + } + } + + public boolean loadDownloadableTexture(String url, String localTexture, ImageParser imageParser) { + if (url == null) { + if (localTexture != null) { + this.bindTexture(this.getTexture(localTexture)); + return true; + } else { + return false; + } + } else { + DownloadedTexture texture = (DownloadedTexture)this.downloadedTextures.get(url); + if (texture == null) { + texture = new DownloadedTexture(null, imageParser); + if( url.startsWith("offlineSkinLocal") ) { + if( OfflineSkinMod.skins.containsKey(url.substring(17)) ) { + texture.image = OfflineSkinMod.skins.get(url.substring(17)).getSkin(); + } else { + texture.image = OfflineSkinMod.skinImage; + } + } else if( url.startsWith("offlineCapeLocal") ) { + if( OfflineSkinMod.skins.containsKey(url.substring(17))) { + texture.image = OfflineSkinMod.skins.get(url.substring(17)).getCape(); + } else { + texture.image = OfflineSkinMod.capeImage; + } + } else { + texture = new DownloadedTexture(url, imageParser); + } + this.downloadedTextures.put(url, texture); + } + + if (texture.textureId < 0 && texture.image != null) { + texture.textureId = this.allocateAndSetupTexture(texture.image); + } + + if (texture.textureId > 0) { + this.bindTexture(texture.textureId); + return true; + } else if (localTexture != null) { + this.bindTexture(this.getTexture(localTexture)); + return true; + } else { + return false; + } + } + } +} diff --git a/src/main/java/alterwain/offlineskin/packet/Packet244SkinRequest.java b/src/main/java/alterwain/offlineskin/packet/Packet244SkinRequest.java new file mode 100644 index 0000000..7137859 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/packet/Packet244SkinRequest.java @@ -0,0 +1,48 @@ +package alterwain.offlineskin.packet; + +import alterwain.offlineskin.SkinRequestHandler; +import net.minecraft.core.net.handler.NetHandler; +import net.minecraft.core.net.packet.Packet; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class Packet244SkinRequest extends Packet { + + private boolean requestSkin; + private boolean requestCape; + private boolean requestModelType; + + public Packet244SkinRequest() {} + + public Packet244SkinRequest(boolean requestSkin, boolean requestCape, boolean requestModelType) { + this.requestSkin = requestSkin; + this.requestCape = requestCape; + this.requestModelType = requestModelType; + } + + @Override + public void readPacketData(DataInputStream dis) throws IOException { + this.requestSkin = dis.readBoolean(); + this.requestCape = dis.readBoolean(); + this.requestModelType = dis.readBoolean(); + } + + @Override + public void writePacketData(DataOutputStream dos) throws IOException { + dos.writeBoolean(this.requestSkin); + dos.writeBoolean(this.requestCape); + dos.writeBoolean(this.requestModelType); + } + + @Override + public void processPacket(NetHandler netHandler) { + ((SkinRequestHandler) netHandler).offlineSkinChanger$handleSkinRequest(this); + } + + @Override + public int getPacketSize() { + return 3; + } +} diff --git a/src/main/java/alterwain/offlineskin/packet/Packet245SkinResponse.java b/src/main/java/alterwain/offlineskin/packet/Packet245SkinResponse.java new file mode 100644 index 0000000..9d95377 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/packet/Packet245SkinResponse.java @@ -0,0 +1,82 @@ +package alterwain.offlineskin.packet; + +import alterwain.offlineskin.SkinResponseHandler; +import net.minecraft.core.net.handler.NetHandler; +import net.minecraft.core.net.packet.Packet; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class Packet245SkinResponse extends Packet { + private String username; + private byte[] skin; + private byte[] cape; + private boolean modelType; + + public Packet245SkinResponse(String username, byte[] skin, byte[] cape, boolean modelType) { + this.username = username; + this.skin = skin; + this.cape = cape; + this.modelType = modelType; + } + + public Packet245SkinResponse() { + this.username = ""; + this.skin = new byte[0]; + this.cape = new byte[0]; + this.modelType = false; + } + + @Override + public void readPacketData(DataInputStream dis) throws IOException { + int nameLen = dis.readInt(); + byte[] ub = new byte[nameLen]; + dis.read(ub); + this.username = new String(ub); + int skinLen = dis.readInt(); + this.skin = new byte[skinLen]; + dis.read(this.skin); + int capeLen = dis.readInt(); + this.cape = new byte[capeLen]; + dis.read(cape); + this.modelType = dis.readBoolean(); + } + + @Override + public void writePacketData(DataOutputStream dos) throws IOException { + dos.writeInt(username.length()); + dos.write(username.getBytes()); + dos.writeInt(skin.length); + dos.write(skin); + dos.writeInt(cape.length); + dos.write(cape); + dos.writeBoolean(modelType); + } + + @Override + public void processPacket(NetHandler netHandler) { + ((SkinResponseHandler) netHandler).offlineSkinChanger$handleSkinResponse(this); + } + + public byte[] getSkin() { + return skin; + } + + public byte[] getCape() { + return cape; + } + + public boolean isModelType() { + return modelType; + } + + public String getUsername() { + return username; + } + + @Override + public int getPacketSize() { + return skin.length+cape.length+username.length()+13; + } +} diff --git a/src/main/java/alterwain/offlineskin/packet/Packet246SkinSet.java b/src/main/java/alterwain/offlineskin/packet/Packet246SkinSet.java new file mode 100644 index 0000000..e02a1f0 --- /dev/null +++ b/src/main/java/alterwain/offlineskin/packet/Packet246SkinSet.java @@ -0,0 +1,78 @@ +package alterwain.offlineskin.packet; + +import alterwain.offlineskin.OfflineSkinMod; +import alterwain.offlineskin.SendSet; +import alterwain.offlineskin.SkinConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.render.EntityRenderDispatcher; +import net.minecraft.client.render.entity.PlayerRenderer; +import net.minecraft.core.entity.player.EntityPlayer; +import net.minecraft.core.net.handler.NetHandler; +import net.minecraft.core.net.packet.Packet; + +import java.awt.image.BufferedImage; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class Packet246SkinSet extends Packet { + + private String username; + private byte[] skin; + private byte[] cape; + private boolean modelType; + + public Packet246SkinSet(String username, byte[] skin, byte[] cape, boolean modelType) { + this.username = username; + this.skin = skin; + this.cape = cape; + this.modelType = modelType; + } + + public Packet246SkinSet() { + this.username = ""; + this.skin = new byte[0]; + this.cape = new byte[0]; + this.modelType = false; + } + + @Override + public void readPacketData(DataInputStream dis) throws IOException { + int nameLen = dis.readInt(); + byte[] ub = new byte[nameLen]; + dis.read(ub); + this.username = new String(ub); + int skinLen = dis.readInt(); + this.skin = new byte[skinLen]; + dis.read(this.skin); + int capeLen = dis.readInt(); + this.cape = new byte[capeLen]; + dis.read(cape); + this.modelType = dis.readBoolean(); + } + + @Override + public void writePacketData(DataOutputStream dos) throws IOException { + dos.writeInt(username.length()); + dos.write(username.getBytes()); + dos.writeInt(skin.length); + dos.write(skin); + dos.writeInt(cape.length); + dos.write(cape); + dos.writeBoolean(modelType); + } + + @Override + public void processPacket(NetHandler netHandler) { + BufferedImage skin1 = OfflineSkinMod.bytesToImage(this.skin); + BufferedImage cape1 = OfflineSkinMod.bytesToImage(this.cape); + OfflineSkinMod.skins.put(this.username, new SkinConfig(skin1, cape1, this.modelType)); + new SendSet(this.username, false).start(); + new SendSet(this.username, true).start(); + } + + @Override + public int getPacketSize() { + return skin.length+cape.length+username.length()+13; + } +} diff --git a/src/main/java/turniplabs/examplemod/ExampleMod.java b/src/main/java/turniplabs/examplemod/ExampleMod.java deleted file mode 100644 index c87f85d..0000000 --- a/src/main/java/turniplabs/examplemod/ExampleMod.java +++ /dev/null @@ -1,38 +0,0 @@ -package turniplabs.examplemod; - -import net.fabricmc.api.ModInitializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import turniplabs.halplibe.helper.BlockBuilder; -import turniplabs.halplibe.util.GameStartEntrypoint; -import turniplabs.halplibe.util.RecipeEntrypoint; - - -public class ExampleMod implements ModInitializer, GameStartEntrypoint, RecipeEntrypoint { - public static final String MOD_ID = "examplemod"; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - @Override - public void onInitialize() { - LOGGER.info("ExampleMod initialized."); - } - - @Override - public void beforeGameStart() { - - } - - @Override - public void afterGameStart() { - - } - - @Override - public void onRecipesReady() { - - } - - @Override - public void initNamespaces() { - - } -} diff --git a/src/main/resources/examplemod.mixins.json b/src/main/resources/examplemod.mixins.json deleted file mode 100644 index e98223c..0000000 --- a/src/main/resources/examplemod.mixins.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "required": true, - "minVersion": "0.8", - "package": "turniplabs.examplemod.mixin", - "compatibilityLevel": "JAVA_8", - "mixins": [ - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index fa99003..ce025b4 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,16 +1,16 @@ { "schemaVersion": 1, - "id": "examplemod", + "id": "offlineskin", "version": "${version}", - "name": "Example Mod", - "description": "This mod aims to help new BTA modders.", + "name": "Offline Skins", + "description": "This mod allows you to change skin/cape without purchased Minecraft.", "authors": [ - "Turnip Labs" + "alterwain" ], "contact": { - "homepage": "", - "sources": "" + "homepage": "https://github.com/alterdekim/OfflineSkinChanger", + "sources": "https://github.com/alterdekim/OfflineSkinChanger" }, "icon": "icon.png", @@ -18,21 +18,21 @@ "environment": "*", "entrypoints": { - "main": [ - "turniplabs.examplemod.ExampleMod" - ], + "main": [ + "alterwain.offlineskin.OfflineSkinMod" + ], "beforeGameStart": [ - "turniplabs.examplemod.ExampleMod" + "alterwain.offlineskin.OfflineSkinMod" ], "afterGameStart": [ - "turniplabs.examplemod.ExampleMod" + "alterwain.offlineskin.OfflineSkinMod" ], "recipesReady": [ - "turniplabs.examplemod.ExampleMod" + "alterwain.offlineskin.OfflineSkinMod" ] }, "mixins": [ - "examplemod.mixins.json" + "offlineskin.mixins.json" ], "depends": { diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png index c64f12d..a34b380 100644 Binary files a/src/main/resources/icon.png and b/src/main/resources/icon.png differ diff --git a/src/main/resources/lang/examplemod/en_US.lang b/src/main/resources/lang/examplemod/en_US.lang deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/lang/offlineskin/en_US.lang b/src/main/resources/lang/offlineskin/en_US.lang new file mode 100644 index 0000000..fb70890 --- /dev/null +++ b/src/main/resources/lang/offlineskin/en_US.lang @@ -0,0 +1,5 @@ +gui.options.page.general.button.edit_skin=Edit player\'s skin +gui.options.page.edit_skin.button.load_skin=Load skin +gui.options.page.edit_skin.button.load_cape=Load cape +gui.options.page.edit_skin.button.close=Close +gui.options.page.edit_skin.label.title=Change skin/cape diff --git a/src/main/resources/offlineskin.mixins.json b/src/main/resources/offlineskin.mixins.json new file mode 100644 index 0000000..f823ab0 --- /dev/null +++ b/src/main/resources/offlineskin.mixins.json @@ -0,0 +1,20 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "alterwain.offlineskin.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "NetClientHandlerMixin", + "NetServerHandlerMixin", + "OptionsPagesMixin", + "PacketMixin", + "PlayerManagerMixin", + "PlayerRendererMixin", + "RenderEngineMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +}