Compare commits

...

10 Commits

Author SHA1 Message Date
TheBrokenRail fcb7df74ed Add Poster
Gestus/pipeline/head This commit looks good Details
2020-08-21 08:22:46 -04:00
TheBrokenRail b2ec9ea567 Remove Extra Newline
Gestus/pipeline/head This commit looks good Details
2020-08-15 13:05:13 -04:00
TheBrokenRail 96860fa2c1 Add Logging
Gestus/pipeline/head This commit looks good Details
2020-08-15 12:51:01 -04:00
TheBrokenRail 17687a3056 1.0.3
Gestus/pipeline/head This commit looks good Details
2020-08-14 23:31:51 -04:00
TheBrokenRail 419b2c1f21 1.0.2
Gestus/pipeline/head This commit looks good Details
2020-08-14 21:15:59 -04:00
TheBrokenRail 26492891a6 Improve Packet Handling
Gestus/pipeline/head This commit looks good Details
2020-08-14 16:54:11 -04:00
TheBrokenRail 248f925b99 Fix
Gestus/pipeline/head This commit looks good Details
2020-08-11 22:20:27 -04:00
TheBrokenRail 6cedb984fe 1.0.1
Gestus/pipeline/head This commit looks good Details
2020-08-11 22:10:21 -04:00
TheBrokenRail 5d08b027e6 Remove Pitch Edge-Case
Gestus/pipeline/head This commit looks good Details
2020-08-11 19:22:12 -04:00
TheBrokenRail 7f985e24b1 Improve Documentation
Gestus/pipeline/head This commit looks good Details
2020-08-11 18:27:36 -04:00
29 changed files with 475 additions and 215 deletions

View File

@ -1,4 +1,13 @@
# Changelog
**1.0.3**
* Remove ``main_arm`` And ``off_arm``
**1.0.2**
* Add Eating Animation
**1.0.1**
* Average Skin Color In Default Leather Armor
**1.0.0**
* Initial Release

View File

@ -5,6 +5,7 @@
- In ideal conditions emotes run at 20FPS
- All frames are made up of part rotations separated by spaces
- Parts can only be specified once in a frame
- If a line is ``maintain``, it will copy the previous frame
## Part Rotation Format
```
@ -16,7 +17,4 @@
- ``right_leg``
- ``left_arm``
- ``right_arm``
- ``main_arm``
- ``off_arm``
- ``body``
- ``left_arm``/``right_arm`` is not compatible with ``main_arm``/``off_arm``
- ``body``

View File

@ -1,6 +1,12 @@
![Poster](images/poster.png)
# Gestus
A server-side mod that adds data-driven emotes to Minecraft that can be viewed by vanilla clients.
- All Built-In Animations/Poses (Walking, Running, Shield Blocking) Are In ``gestus:builtin/`` And Can Be Overridden
- ``/emote <emote> <players>`` Requires OP
- ``/emote <emote>`` Can Be Run By All Players
## File Format
[View File Format](FILE_FORMAT.md)

View File

@ -6,11 +6,11 @@ org.gradle.jvmargs = -Xmx1G
minecraft_version = 1.16.2
curseforge_id = 401707
simple_minecraft_version = 1.16.2
yarn_build = 1
yarn_build = 12
fabric_loader_version = 0.9.0+build.204
# Mod Properties
mod_version = 1.0.0
mod_version = 1.0.3
maven_group = com.thebrokenrail
# Dependencies

BIN
images/poster.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -11,16 +11,6 @@ import java.util.Map;
public class Emote {
public final EmoteFrame[] frames;
private static int getArmMode(EmotePart part) {
if (part == EmotePart.LEFT_ARM || part == EmotePart.RIGHT_ARM) {
return 1;
} else if (part == EmotePart.MAIN_ARM || part == EmotePart.OFF_ARM) {
return 2;
} else {
return 0;
}
}
public Emote(String[] lines) throws EmoteSyntaxException {
List<EmoteFrame> list = new ArrayList<>();
@ -29,11 +19,13 @@ public class Emote {
for (int lineNum = 0; lineNum < lines.length; lineNum++) {
String line = lines[lineNum].trim();
if (line.equals("maintain") && list.size() > 0) {
list.add(list.get(list.size() - 1));
if (line.equals("maintain")) {
if (list.size() > 0) {
list.add(list.get(list.size() - 1));
} else {
throw new EmoteSyntaxException(lineNum, "No Previous Frame");
}
} else if (!line.startsWith("#")) {
int armMode = 0;
Map<EmotePart, EulerAngle> map = new HashMap<>();
String[] lineParts = line.split(" ");
@ -55,13 +47,6 @@ public class Emote {
throw new EmoteSyntaxException(lineNum, "Invalid Emote Part: " + linePart);
} else if (map.containsKey(selectedPart)) {
throw new EmoteSyntaxException(lineNum, "Duplicate Emote Part: " + linePart);
} else if (armMode == 0) {
armMode = getArmMode(selectedPart);
} else if (armMode > 0) {
int newMode = getArmMode(selectedPart);
if (newMode != 0 && newMode != armMode) {
throw new EmoteSyntaxException(lineNum, "Conflicting Arm Modes");
}
}
try {
@ -70,7 +55,7 @@ public class Emote {
String rollStr = lineParts[++i];
float yaw = Float.parseFloat(yawStr);
float pitch = -Float.parseFloat(pitchStr);
float pitch = Float.parseFloat(pitchStr);
float roll = Float.parseFloat(rollStr);
map.put(selectedPart, new EulerAngle(pitch, yaw, roll));

View File

@ -6,32 +6,19 @@ import java.util.HashMap;
import java.util.Map;
public class EmoteFrame {
public final Map<EmotePart, EulerAngle> rightHanded;
public final Map<EmotePart, EulerAngle> leftHanded;
public final Map<EmotePart, EulerAngle> normal;
public final Map<EmotePart, EulerAngle> mirrored;
public EmoteFrame(Map<EmotePart, EulerAngle> data) {
rightHanded = resolve(data, false);
leftHanded = resolve(data, true);
normal = data;
mirrored = mirror(data);
}
private static Map<EmotePart, EulerAngle> resolve(Map<EmotePart, EulerAngle> data, boolean leftHanded) {
private static Map<EmotePart, EulerAngle> mirror(Map<EmotePart, EulerAngle> data) {
Map<EmotePart, EulerAngle> result = new HashMap<>();
for (Map.Entry<EmotePart, EulerAngle> entry : data.entrySet()) {
if (entry.getKey() == EmotePart.MAIN_ARM) {
if (leftHanded) {
result.put(EmotePart.LEFT_ARM, new EulerAngle(entry.getValue().getPitch(), -entry.getValue().getYaw(), -entry.getValue().getRoll()));
} else {
result.put(EmotePart.RIGHT_ARM, entry.getValue());
}
} else if (entry.getKey() == EmotePart.OFF_ARM) {
if (leftHanded) {
result.put(EmotePart.RIGHT_ARM, new EulerAngle(entry.getValue().getPitch(), -entry.getValue().getYaw(), -entry.getValue().getRoll()));
} else {
result.put(EmotePart.LEFT_ARM, entry.getValue());
}
} else {
result.put(entry.getKey(), entry.getValue());
}
EmotePart newPart = entry.getKey().mirror();
result.put(newPart, new EulerAngle(entry.getValue().getPitch(), -entry.getValue().getYaw(), -entry.getValue().getRoll()));
}
return result;
}

View File

@ -12,7 +12,7 @@ public class EmoteLayer {
private Identifier current;
private final boolean loop;
private boolean leftHanded = false;
private boolean mirrored = false;
public EmoteLayer(Identifier start, boolean loop) {
current = start;
@ -23,10 +23,10 @@ public class EmoteLayer {
return current;
}
public void play(Identifier current, boolean leftHanded) {
if (!Objects.equals(this.current, current) || this.leftHanded != leftHanded) {
public void play(Identifier current, boolean mirrored) {
if (!Objects.equals(this.current, current) || this.mirrored != mirrored) {
this.current = current;
this.leftHanded = leftHanded;
this.mirrored = mirrored;
frame = 0;
}
}
@ -35,8 +35,8 @@ public class EmoteLayer {
return current != null;
}
public boolean isLeftHanded() {
return leftHanded;
public boolean isMirrored() {
return mirrored;
}
public Map<EmotePart, EulerAngle> next() {
@ -53,7 +53,7 @@ public class EmoteLayer {
current = null;
}
}
return leftHanded ? result.leftHanded : result.rightHanded;
return mirrored ? result.mirrored : result.normal;
}
}
frame = 0;

View File

@ -1,13 +1,36 @@
package com.thebrokenrail.gestus.emote;
public enum EmotePart {
MAIN_ARM("main_arm"),
OFF_ARM("off_arm"),
RIGHT_ARM("right_arm"),
LEFT_ARM("left_arm"),
RIGHT_LEG("right_leg"),
LEFT_LEG("left_leg"),
BODY("body");
RIGHT_ARM("right_arm") {
@Override
public EmotePart mirror() {
return LEFT_ARM;
}
},
LEFT_ARM("left_arm") {
@Override
public EmotePart mirror() {
return RIGHT_ARM;
}
},
RIGHT_LEG("right_leg") {
@Override
public EmotePart mirror() {
return LEFT_LEG;
}
},
LEFT_LEG("left_leg") {
@Override
public EmotePart mirror() {
return RIGHT_LEG;
}
},
BODY("body") {
@Override
public EmotePart mirror() {
return BODY;
}
};
private final String name;
@ -18,4 +41,6 @@ public enum EmotePart {
public String getName() {
return name;
}
public abstract EmotePart mirror();
}

View File

@ -4,19 +4,24 @@ import com.thebrokenrail.gestus.Gestus;
import com.thebrokenrail.gestus.emote.EmoteLayer;
import com.thebrokenrail.gestus.emote.EmotePart;
import com.thebrokenrail.gestus.mixin.ArmorStandEntityAccessor;
import com.thebrokenrail.gestus.skin.SkinColor;
import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.CrossbowItem;
import net.minecraft.item.DyeableItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Arm;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
@ -44,8 +49,9 @@ public class FakePlayerEntity extends ArmorStandEntity {
private static final Identifier HIT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/hit"); // Hit Layer
private static final Identifier AIM_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/aim"); // Aim Layer
private static final Identifier TRIDENT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/trident"); // Aim Layer
private static final Identifier SHIELD_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/shield"); // Aim Layer
private static final Identifier TRIDENT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/trident"); // Aim Layer
private static final Identifier EAT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/eat"); // Aim Layer
public final ServerPlayerEntity player;
@ -172,21 +178,26 @@ public class FakePlayerEntity extends ArmorStandEntity {
emote = TRIDENT_EMOTE;
break;
}
case EAT:
case DRINK: {
emote = EAT_EMOTE;
break;
}
}
}
if (emote != null) {
layers[AIM_LAYER].play(emote, (hand == Hand.OFF_HAND) != isLeftHanded());
return true;
} else {
return false;
} else {
return true;
}
}
private void updateAim() {
Hand hand = player.getActiveHand();
if (!updateAim(hand, true)) {
updateAim(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND, false);
if (updateAim(hand, true) && updateAim(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND, false)) {
layers[AIM_LAYER].play(null, false);
}
}
@ -202,6 +213,25 @@ public class FakePlayerEntity extends ArmorStandEntity {
}
}
private SkinColor skinColor = null;
private void updateSkinColor() {
if (skinColor == null) {
skinColor = SkinColor.get(player.getUuid());
if (skinColor == null) {
skinColor = SkinColor.blank();
}
}
}
private ItemStack colorItem(Item item, int color) {
ItemStack stack = new ItemStack(item);
if (color != -1) {
((DyeableItem) item).setColor(stack, color);
}
return stack;
}
private ItemStack getFallbackItem(EquipmentSlot slot, ItemStack stack, boolean invisible) {
if (stack.isEmpty() && !invisible) {
switch (slot) {
@ -210,13 +240,16 @@ public class FakePlayerEntity extends ArmorStandEntity {
return head;
}
case CHEST: {
return new ItemStack(Items.LEATHER_CHESTPLATE);
updateSkinColor();
return colorItem(Items.LEATHER_CHESTPLATE, skinColor.chest);
}
case LEGS: {
return new ItemStack(Items.LEATHER_LEGGINGS);
updateSkinColor();
return colorItem(Items.LEATHER_LEGGINGS, skinColor.legs);
}
case FEET: {
return new ItemStack(Items.LEATHER_BOOTS);
updateSkinColor();
return colorItem(Items.LEATHER_BOOTS, skinColor.boots);
}
default: {
return ItemStack.EMPTY;
@ -233,7 +266,7 @@ public class FakePlayerEntity extends ArmorStandEntity {
private void updateEquipment() {
for (EquipmentSlot slot : EquipmentSlot.values()) {
ItemStack target = getFallbackItem(slot, player.getEquippedStack(slot).copy(), isInvisible());
ItemStack target = getFallbackItem(slot, player.getEquippedStack(slot).copy(), isInvisible()).copy();
if (!getEquippedStack(slot).isItemEqual(target)) {
equipStack(slot, target);
}
@ -268,4 +301,14 @@ public class FakePlayerEntity extends ArmorStandEntity {
public boolean damage(DamageSource source, float amount) {
return player.damage(source, amount);
}
@Override
public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) {
return ActionResult.PASS;
}
@Override
public ActionResult interact(PlayerEntity player, Hand hand) {
return ActionResult.PASS;
}
}

View File

@ -0,0 +1,24 @@
package com.thebrokenrail.gestus.mixin;
import com.mojang.datafixers.util.Pair;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.EntityEquipmentUpdateS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(EntityEquipmentUpdateS2CPacket.class)
public interface EntityEquipmentUpdateS2CPacketAccessor {
@Accessor
int getId();
@Accessor
List<Pair<EquipmentSlot, ItemStack>> getEquipmentList();
@Mutable
@Accessor
void setEquipmentList(List<Pair<EquipmentSlot, ItemStack>> equipmentList);
}

View File

@ -1,27 +0,0 @@
package com.thebrokenrail.gestus.mixin;
import com.mojang.datafixers.util.Pair;
import com.thebrokenrail.gestus.util.Util;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.EntityTrackerEntry;
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.ModifyArg;
import java.util.List;
@Mixin(EntityTrackerEntry.class)
public class MixinEntityTrackerEntry {
@Shadow
@Final
private Entity entity;
@ModifyArg(at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityEquipmentUpdateS2CPacket;<init>(ILjava/util/List;)V"), method = "*", index = 1)
public List<Pair<EquipmentSlot, ItemStack>> modifyEquipment(List<Pair<EquipmentSlot, ItemStack>> list) {
return Util.modifyEquipment(entity, list);
}
}

View File

@ -1,20 +0,0 @@
package com.thebrokenrail.gestus.mixin;
import com.mojang.datafixers.util.Pair;
import com.thebrokenrail.gestus.util.Util;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.List;
@Mixin(LivingEntity.class)
public class MixinLivingEntity {
@ModifyArg(at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityEquipmentUpdateS2CPacket;<init>(ILjava/util/List;)V"), method = "*", index = 1)
public List<Pair<EquipmentSlot, ItemStack>> modifyEquipment(List<Pair<EquipmentSlot, ItemStack>> list) {
return Util.modifyEquipment((LivingEntity) (Object) this, list);
}
}

View File

@ -5,13 +5,16 @@ import com.thebrokenrail.gestus.util.Util;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.network.packet.s2c.play.EntityEquipmentUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@ -33,10 +36,17 @@ public class MixinServerPlayNetworkHandler {
}
}
@Unique
private boolean shouldModify(int id) {
return player.getEntityWorld().getEntityById(id) instanceof PlayerEntity && id != player.getEntityId();
}
@ModifyVariable(at = @At("HEAD"), method = "sendPacket(Lnet/minecraft/network/Packet;Lio/netty/util/concurrent/GenericFutureListener;)V", argsOnly = true)
public Packet<?> modifyPacket(Packet<?> packet) {
if (packet instanceof EntityTrackerUpdateS2CPacket && ((EntityTrackerUpdateS2CPacketAccessor) packet).getId() == player.getEntityId()) {
if (packet instanceof EntityTrackerUpdateS2CPacket && shouldModify(((EntityTrackerUpdateS2CPacketAccessor) packet).getId())) {
return Util.modifyTracker(player, (EntityTrackerUpdateS2CPacket) packet);
} else if (packet instanceof EntityEquipmentUpdateS2CPacket && shouldModify(((EntityEquipmentUpdateS2CPacketAccessor) packet).getId())) {
return Util.modifyEquipment((EntityEquipmentUpdateS2CPacket) packet);
} else {
return packet;
}

View File

@ -4,7 +4,6 @@ import com.mojang.authlib.GameProfile;
import com.thebrokenrail.gestus.Gestus;
import com.thebrokenrail.gestus.entity.FakePlayerEntity;
import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Hand;
@ -18,6 +17,8 @@ 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.Objects;
@Mixin(ServerPlayerEntity.class)
public abstract class MixinServerPlayerEntity extends PlayerEntity implements ServerPlayerEntityExtension {
@Unique
@ -40,17 +41,23 @@ public abstract class MixinServerPlayerEntity extends PlayerEntity implements Se
spawn = true;
}
boolean invisible = hasStatusEffect(StatusEffects.INVISIBILITY);
boolean glowing = hasStatusEffect(StatusEffects.GLOWING);
boolean invisible = isInvisible();
shadow.setInvisible(invisible);
shadow.setGlowing(glowing);
shadow.setGlowing(isGlowing());
shadow.setWorld(getEntityWorld());
shadow.refreshPositionAndAngles(getX(), getY(), getZ(), yaw, pitch);
shadow.setHeadRotation(new EulerAngle(pitch, getHeadYaw() - yaw, 0f));
shadow.setVelocity(getVelocity());
Vec3d olVVelocity = shadow.getVelocity();
Vec3d velocity = getVelocity();
if (!Objects.equals(olVVelocity, velocity)) {
shadow.setVelocity(velocity);
shadow.velocityDirty = true;
shadow.velocityModified = true;
}
shadow.setCustomNameVisible(!invisible && !isSneaking());
@ -66,8 +73,6 @@ public abstract class MixinServerPlayerEntity extends PlayerEntity implements Se
@Inject(at = @At("HEAD"), method = "tick")
public void tick(CallbackInfo info) {
setInvisible(true);
setGlowing(false);
updateShadow();
lastPos = getPos();
}

View File

@ -0,0 +1,116 @@
package com.thebrokenrail.gestus.skin;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
public final class SkinColor {
public final int chest;
public final int legs;
public final int boots;
private SkinColor(int chest, int legs, int boots) {
this.chest = chest;
this.legs = legs;
this.boots = boots;
}
private static class Color {
private final int data;
private Color(int data) {
this.data = data;
}
private Color(int red, int green, int blue) {
data = 255 << 24 | (red & 255) << 16 | (green & 255) << 8 | (blue & 255);
}
private Color(long red, long green, long blue) {
this((int) red, (int) green, (int) blue);
}
public int getRed() {
return data >> 16 & 255;
}
public int getGreen() {
return data >> 8 & 255;
}
public int getBlue() {
return data & 255;
}
public int getData() {
return (getRed() << 16) | (getGreen() << 8) | getBlue();
}
}
private static Color averageColor(BufferedImage image, int x0, int y0, int w, int h) {
int x1 = x0 + w;
int y1 = y0 + h;
long sumRed = 0;
long sumGreen = 0;
long sumBlue = 0;
int num = 0;
for (int x = x0; x < x1; x++) {
for (int y = y0; y < y1; y++) {
int data = image.getRGB(x, y);
if ((data >> 24 & 255) == 255) {
Color pixel = new Color(data);
sumRed += pixel.getRed();
sumGreen += pixel.getGreen();
sumBlue += pixel.getBlue();
num++;
}
}
}
return new Color(sumRed / num, sumGreen / num, sumBlue / num);
}
private static Color averageColor(Color color1, Color color2) {
long sumRed = color1.getRed() + color2.getRed();
long sumGreen = color1.getGreen() + color2.getGreen();
long sumBlue = color1.getBlue() + color2.getBlue();
return new Color(sumRed / 2, sumGreen / 2, sumBlue / 2);
}
public static SkinColor get(UUID uuid) {
String skin = SkinJSON.get(uuid);
if (skin != null) {
URL url;
try {
url = new URL(skin);
} catch (MalformedURLException e) {
return null;
}
BufferedImage image;
try {
image = ImageIO.read(url);
} catch (IOException e) {
return null;
}
int chest = averageColor(image, 16, 20, 23, 11).getData();
int legs = averageColor(averageColor(image, 16, 52, 11, 11), averageColor(image, 0, 20, 11, 11)).getData();
int boots = averageColor(averageColor(image, 20, 48, 7, 3), averageColor(image, 44, 16, 7, 3)).getData();
return new SkinColor(chest, legs, boots);
} else {
return null;
}
}
public static SkinColor blank() {
return new SkinColor(-1, -1, -1);
}
}

View File

@ -0,0 +1,99 @@
package com.thebrokenrail.gestus.skin;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.apache.logging.log4j.LogManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
final class SkinJSON {
private static class Response {
public Property[] properties = null;
}
private static class Property {
public String name = null;
public String value = null;
}
private static class Data {
public Map<String, Texture> textures;
}
private static class Texture {
public String url = null;
}
private static String urlToString(String urlStr) {
try {
StringBuilder stringBuilder = new StringBuilder();
URL url = new URL(urlStr);
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append('\n');
}
}
return stringBuilder.toString();
} catch (IOException e) {
return null;
}
}
static void error(String data) {
LogManager.getLogger("Gestus").error(data);
}
private static final String SKIN_TEXTURE_KEY = "SKIN";
static String get(UUID uuid) {
String url = String.format("https://sessionserver.mojang.com/session/minecraft/profile/%s", uuid.toString().replace("-", ""));
String data = urlToString(url);
if (data != null) {
try {
Gson gson = new Gson();
Response response = gson.fromJson(data, Response.class);
if (response != null && response.properties != null) {
for (Property property : response.properties) {
if ("textures".equals(property.name)) {
try {
String texturesJSON = new String(Base64.getDecoder().decode(property.value));
Data textures = gson.fromJson(texturesJSON, Data.class);
if (textures.textures.containsKey(SKIN_TEXTURE_KEY)) {
Texture skin = textures.textures.get(SKIN_TEXTURE_KEY);
return skin.url;
}
break;
} catch (JsonSyntaxException e) {
error("Unable To Parse Embedded Skin Metadata: " + uuid.toString());
return null;
}
}
}
}
error("Embedded Skin Metadata Is Invalid: " + uuid.toString());
return null;
} catch (JsonSyntaxException e) {
error("Unable To Parse Skin Metadata: " + uuid);
return null;
}
} else {
error("Unable To Download Skin Metadata: " + uuid);
return null;
}
}
}

View File

@ -2,30 +2,26 @@ package com.thebrokenrail.gestus.util;
import com.mojang.datafixers.util.Pair;
import com.thebrokenrail.gestus.mixin.EntityAccessor;
import com.thebrokenrail.gestus.mixin.EntityEquipmentUpdateS2CPacketAccessor;
import com.thebrokenrail.gestus.mixin.EntityTrackerUpdateS2CPacketAccessor;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.EntityEquipmentUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
import java.util.ArrayList;
import java.util.List;
public class Util {
public static List<Pair<EquipmentSlot, ItemStack>> modifyEquipment(Entity entity, List<Pair<EquipmentSlot, ItemStack>> list) {
if (entity instanceof PlayerEntity) {
List<Pair<EquipmentSlot, ItemStack>> newList = new ArrayList<>();
for (EquipmentSlot slot : EquipmentSlot.values()) {
newList.add(Pair.of(slot, ItemStack.EMPTY));
}
return newList;
} else {
return list;
public static EntityEquipmentUpdateS2CPacket modifyEquipment(EntityEquipmentUpdateS2CPacket packet) {
List<Pair<EquipmentSlot, ItemStack>> newList = new ArrayList<>();
for (EquipmentSlot slot : EquipmentSlot.values()) {
newList.add(Pair.of(slot, ItemStack.EMPTY));
}
return new EntityEquipmentUpdateS2CPacket(((EntityEquipmentUpdateS2CPacketAccessor) packet).getId(), newList);
}
private static final int INVISIBILITY_FLAG = 5;
@ -48,8 +44,8 @@ public class Util {
for (DataTracker.Entry<?> entry : entries) {
if (entry.getData() == flags) {
byte data = (Byte) entry.get();
data = setFlag(data, INVISIBILITY_FLAG, entity.hasStatusEffect(StatusEffects.INVISIBILITY));
data = setFlag(data, GLOWING_FLAG, entity.hasStatusEffect(StatusEffects.GLOWING));
data = setFlag(data, INVISIBILITY_FLAG, true);
data = setFlag(data, GLOWING_FLAG, false);
newEntries.add(new DataTracker.Entry<>(flags, data));
} else {
newEntries.add(entry);

View File

@ -1 +1 @@
main_arm 0 90 0 off_arm 40 90 0
right_arm 0 -90 0 left_arm 40 -90 0

View File

@ -0,0 +1,5 @@
right_arm -51 -76 0
right_arm -55 -80 0
right_arm -59 -84 0
right_arm -55 -80 0
right_arm -51 -76 0

View File

@ -1,9 +1,9 @@
main_arm 0 0 0
main_arm -8.5 17 0
main_arm -17 34 0
main_arm -25.5 51 0
main_arm -34 68 0
main_arm -25.5 51 0
main_arm -17 34 0
main_arm -8.5 17 0
main_arm 0 0 0
right_arm 0 0 0
right_arm -8.5 -17 0
right_arm -17 -34 0
right_arm -25.5 -51 0
right_arm -34 -68 0
right_arm -25.5 -51 0
right_arm -17 -34 0
right_arm -8.5 -17 0
right_arm 0 0 0

View File

@ -1 +1 @@
right_leg 24 71 0 left_leg -24 71 0
right_leg 24 -71 0 left_leg -24 -71 0

View File

@ -1 +1 @@
main_arm -42 90 0
right_arm -42 -90 0

View File

@ -1,13 +1,4 @@
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0
right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0
right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0
right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0
right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0
right_arm 0 -42 0 left_arm 0 42 0 left_leg 0 -42 0 right_leg 0 42 0
@ -16,4 +7,13 @@ right_arm 0 -56 0 left_arm 0 56 0 left_leg 0 -56 0 right_leg 0 56 0
right_arm 0 -42 0 left_arm 0 42 0 left_leg 0 -42 0 right_leg 0 42 0
right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0
right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0
right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0
right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0
right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0

View File

@ -1 +1 @@
main_arm 0 0 0 off_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 body 0 0 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 body 0 0 0

View File

@ -1 +1 @@
main_arm 0 180 0
right_arm 0 -180 0

View File

@ -1,15 +1,4 @@
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0
right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 -7 0 left_arm 0 7 0 left_leg 0 -7 0 right_leg 0 7 0
right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0
right_arm 0 -21 0 left_arm 0 21 0 left_leg 0 -21 0 right_leg 0 21 0
@ -20,4 +9,15 @@ right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0
right_arm 0 -21 0 left_arm 0 21 0 left_leg 0 -21 0 right_leg 0 21 0
right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0
right_arm 0 -7 0 left_arm 0 7 0 left_leg 0 -7 0 right_leg 0 7 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0
right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0
right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0
right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0
right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0
right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0
right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0
right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0

View File

@ -1,40 +1,40 @@
main_arm 0 0 0
main_arm 0 0 16
main_arm 0 0 32
main_arm 0 0 48
main_arm 0 0 64
main_arm 0 0 80
main_arm 0 0 96
main_arm 0 0 112
main_arm 0 0 128
main_arm 0 0 144
main_arm 0 0 145
main_arm 0 0 140
main_arm 0 0 135
main_arm 0 0 130
main_arm 0 0 125
main_arm 0 0 120
main_arm 0 0 125
main_arm 0 0 130
main_arm 0 0 135
main_arm 0 0 140
main_arm 0 0 145
main_arm 0 0 145
main_arm 0 0 140
main_arm 0 0 135
main_arm 0 0 130
main_arm 0 0 125
main_arm 0 0 120
main_arm 0 0 125
main_arm 0 0 130
main_arm 0 0 135
main_arm 0 0 140
main_arm 0 0 145
main_arm 0 0 112
main_arm 0 0 96
main_arm 0 0 80
main_arm 0 0 64
main_arm 0 0 48
main_arm 0 0 32
main_arm 0 0 16
main_arm 0 0 0
right_arm 0 0 0
right_arm 0 0 16
right_arm 0 0 32
right_arm 0 0 48
right_arm 0 0 64
right_arm 0 0 80
right_arm 0 0 96
right_arm 0 0 112
right_arm 0 0 128
right_arm 0 0 144
right_arm 0 0 145
right_arm 0 0 140
right_arm 0 0 135
right_arm 0 0 130
right_arm 0 0 125
right_arm 0 0 120
right_arm 0 0 125
right_arm 0 0 130
right_arm 0 0 135
right_arm 0 0 140
right_arm 0 0 145
right_arm 0 0 145
right_arm 0 0 140
right_arm 0 0 135
right_arm 0 0 130
right_arm 0 0 125
right_arm 0 0 120
right_arm 0 0 125
right_arm 0 0 130
right_arm 0 0 135
right_arm 0 0 140
right_arm 0 0 145
right_arm 0 0 112
right_arm 0 0 96
right_arm 0 0 80
right_arm 0 0 64
right_arm 0 0 48
right_arm 0 0 32
right_arm 0 0 16
right_arm 0 0 0

View File

@ -6,9 +6,8 @@
"ArmorStandEntityAccessor",
"DataTrackerAccessor",
"EntityAccessor",
"EntityEquipmentUpdateS2CPacketAccessor",
"EntityTrackerUpdateS2CPacketAccessor",
"MixinEntityTrackerEntry",
"MixinLivingEntity",
"MixinMobSpawnS2CPacket",
"MixinServerPlayerEntity",
"MixinServerPlayNetworkHandler",