Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity Metadata Wrapper #3

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Conversation

Quantum64
Copy link

Added an easy to use wrapper for entity metadata, because it can be hard to understand normally.
The class extends WrappedDataWatcher and adds all methods to get/set the metadata.
This currently works with the SpawnMob class and the SpawnNamedEntity class, but it can be used for all entities in the future.
Users will use this class when normally using WrappedDataWatcher, so it would look like setMetadata(EntityMetadata value);
I did some sample javadoc at the beginning, but it takes so much time to write. I will finish it if you like the idea.
I am also planning to add enums for different types (villager, sheep, horse, etc) and the bit masks for mob flags (right now the class assumes the user know how to bitshift)

@aadnk
Copy link
Owner

aadnk commented Nov 5, 2013

Nice job, but I feel extending WrappedDataWatcher would create unnecessarily clutter. I think the composition pattern is more appropriate here - just delegate all method calls to a WrappedDataWatcher.

I imagine you'd then either retrieve the underlying DataWatcher with getDataWatcher(), or just use Packet14SpawnNamedEntity.setMetadata().

I wonder though, wouldn't it be better to create a different subclass for each entity type (they all share entity or living entity). Since most people merely copy classes from this resource (instead of maven shading), they should probably be inner classes, and easily castable from the superclass EntityMetadata:

public class EntityMetadata {
    public static class LivingEntityMetadata extends EntityMetdata {
        public float getHealth();
        public Color getPotionEffectColor();
        public boolean isPotionEffectAmbient();
        public int getStuckArrowCount();
        public String getNameTag();
        public boolean isNameTagDisplayed();
    }

    public static class AgeableMetadata extends LivingEntityMetadata {
       public int getAge();
    }

    public static class HorseMetadata extends AgeableMetadata {
        public int getHorseFlags();
    // ... and so on
    }

    public int getFlags(); // index 0
    public int getAirBubbles(); // index 1

    public EntityMetadata setFlags(int flags);
    public EntityMetadata setAirBubbles(int count);

    // For easy casting
    public HorseMetadata asHorse();

    // For easy construction
    public static HorseMetadata newHorse();

    protected WrappedDataWatcher watcher;
}

This would add a significant amount of boilerplate code, but I think it would be better to use. I also recommend "builder"-style setters, but it's not essential.

As for flags, they might very well be an IntEnum, like most things in PacketWrapper. But EnumSet is certainly a possible alternative.

Anyways, I'll let you finish it before I pull it.

@Quantum64
Copy link
Author

OK, that make alot more sense than the way I was doing it, i'll probably try to rewrite it that way tomorrow, but do you think that the EntityMetadata could be extended to work with other things too? It has many more uses past mobs and players (projectiles, minecarts, boats, fireworks) I think it could be made into a flexible interface for modifying metadata of all entities.
Metadata type 5 (Slot - Item related) involves transferring compressed NTB data, and I think that is very unlikely to be added to ProtocolLib

@Quantum64
Copy link
Author

I'll get back to you tomorrow; it's (really) late here and I hardly know what i'm typing XD

@Quantum64
Copy link
Author

And its not necessarily needed in every Packet Wrapper project, but if the developer feels like they need a better way to write entity metadata, they can copy it into their project, it's not required or anything.
(Now that I think about it the methods that accept the EntityMetadata would throw a Class Not Found, but I can work around that)

@aadnk
Copy link
Owner

aadnk commented Nov 5, 2013

Yeah, it should work for every entity type, including non-living entities such as arrows or minecarts.

Metadata type 5 (Slot - Item related) involves transferring compressed NTB data, and I think that is very unlikely to be added to ProtocolLib

ProtocolLib supports it - you can write and read an ItemStack directly to the WrappedDataWatcher, and it will do the rest. :)

Dylan Ryman added 2 commits November 5, 2013 20:04
Started work on Javadoc
Started adding enums for bit masks to be nice
@Quantum64
Copy link
Author

Still working on it, not quite finished. Ran into a couple of problems though:

  1. I can't really add the proper return type because the getMeta() method is just the getObject() method form WrappedDataWatcher, I'll probably end up just casting the values in the return statement (I don't like casting much :/)
  2. I wanted to add enums for the bitmasking, but you can only select one option in an enum, which makes it kind of limiting -- but if you want advanced, you shouldn't be using the enum version anyway.
  3. I'm not 100% sure i'm doing the bitmasking correctly myself...

@aadnk
Copy link
Owner

aadnk commented Nov 6, 2013

On a second thought, its probably best not to expose the bit fields as enums. It's really just an implementation detail designed to conserve bandwidth.

Doesn't look like you've mastered bitmasking quite yet, but you're not that far off:

public static class HorseMetadata extends EntityMetadata {
    private static final int HORSE_FLAGS = 16;
    private static final int HORSE_COLORSTYLE = 20;

    // You could move this to EntityMetadata too
    protected boolean getFlag(int index, int mask) {
        return ((Integer) getMeta(index) & mask) != 0;
    }

    protected void setFlag(int index, int mask, boolean value) {
        setMeta(index, ((Integer) getMeta(index) & ~mask) | (value ? mask : 0));
    }

    public boolean isTame() {
        return getFlag(HORSE_FLAGS, 0x02);
    }

    public void setTame(boolean value) {
        setFlag(HORSE_FLAGS, 0x02, value);
    }

    public boolean hasSadle() {
        return getFlag(HORSE_FLAGS, 0x04);
    }

    public void setSadle(boolean value) {
        setFlag(HORSE_FLAGS, 0x04, value);
    }

    // etc.

    public boolean isMouthOpen() {
        return getFlag(HORSE_FLAGS, 0x80);
    }

    public void setMouthOpen(boolean value) {
        setFlag(HORSE_FLAGS, 0x80, value);
    }

    // You need to extract the code with bit masking
    public HorseColor getHorseColor() {
        return HorseColor.fromId((Integer) getMeta(HORSE_COLORSTYLE) & 0xFF);
    }

    public void setHorseColor(HorseColor type) {
        setMeta(HORSE_COLORSTYLE, (Integer)getMeta(HORSE_COLORSTYLE) & 0xFF00 | type.id);
    }

    // Same with the style
    public HorseStyle getHorseStyle() {
        return HorseStyle.fromId((Integer) getMeta(HORSE_COLORSTYLE) >> 16);
    }

    public void setHorseStyle(HorseStyle style) {
        setMeta(HORSE_COLORSTYLE, (Integer)getMeta(HORSE_COLORSTYLE) & 0xFF | (style.id << 16));
    }

    public enum HorseColor {
        WHITE(0),
        CREAMY(1),
        CHESTNUT(2),
        BROWN(3),
        BLACK(4),
        GRAY(5),
        DARK_BROWN(6);
        int id;

        private HorseColor(int id) {
            this.id = id;
        }

        public static HorseColor fromId(int id) {
            for (HorseColor type : values()) {
                if (type.id == id)
                    return type;
            }
            throw new IllegalArgumentException("Unexpected horse type: " + id);
        }
    }

    public enum HorseStyle {
        NONE(0),
        WHITE(1),
        WHITEFIELD(2),
        WHITE_DOTS(3),
        BLACK_DOTS(4);
        int id;

        private HorseStyle(int id) {
            this.id = id;
        }

        public static HorseStyle fromId(int id) {
            for (HorseStyle style : values()) {
                if (style.id == id)
                    return style;
            }
            throw new IllegalArgumentException("Unexpected horse style: " + id);
        }
    }
}

Oh, and just cast the Objects. If the client sent the wrong type, the server will simply disconnect it.

@Quantum64
Copy link
Author

OK, I see, I needed to test if a flag had already been set and just add to that before overwriting the byte completely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants