Last active
June 9, 2019 08:12
-
-
Save copygirl/836c43b843cf7ed8ab343e852e0a0213 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.mcft.copy.wearables.api; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.stream.Stream; | |
| import net.minecraft.entity.Entity; | |
| import net.minecraft.entity.EquipmentSlot; | |
| import net.minecraft.entity.LivingEntity; | |
| /** | |
| * Implemented by all entities extending {@link LivingEntity} through mixin. | |
| * Allows accessing an entity's {@link IWearableSlot IWearableSlots}, getting | |
| * and setting which items are equipped through Wearables. | |
| * <p> | |
| * Use {@link IWearablesEntity#is} to test whether an entity may support Wearables and | |
| * {@link IWearablesEntity#from} to safely get an IWearablesEntity. This is to account | |
| * for the later possibility of implementing Wearables using component-style properties. | |
| */ | |
| public interface IWearablesEntity | |
| { | |
| /** Returns whether this entity has any items equipped | |
| * in Wearables slots, including in Vanilla armor slots. */ | |
| public boolean hasWearables(); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot} in which this entity has equipped Wearables. | |
| * <p> | |
| * May also include items worn in invalid slots. See {@link IWearablesSlot#isValid}. | |
| * | |
| * @return A stream of slots in which there is a worn item, or an empty | |
| * stream if none, or the entity doesn't support Wearables. | |
| */ | |
| public Stream<IWearablesSlot> getEquippedWearables(); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot} in the specified | |
| * {@link WearablesRegion} in which this entity has equipped Wearables. | |
| * <p> | |
| * May also include items worn in invalid slots. See {@link IWearablesSlot#isValid}. | |
| * | |
| * @param region A region such {@link WearablesRegion#CHEST} or {@link WearablesRegion#BACK}. | |
| * @throws IllegalArgumentException Thrown if region is {@code null}. | |
| * @return A stream of slots in which there is a worn item, or an empty | |
| * stream if none, or the entity doesn't support Wearables. | |
| */ | |
| public Stream<IWearablesSlot> getEquippedWearables(WearablesRegion region); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot} in slots with the specified | |
| * {@link WearablesSlotType} in which this entity has equipped Wearables. | |
| * <p> | |
| * May also include items worn in invalid slots. See {@link IWearablesSlot#isValid}. | |
| * | |
| * @param slotType A slot type such as {@link WearablesSlotType#CHESTPLATE}. | |
| * @throws IllegalArgumentException Thrown if slotType is {@code null}. | |
| * @return A stream of slots in which there is a worn item, or an empty | |
| * stream if none, or the entity doesn't support Wearables. | |
| */ | |
| public Stream<IWearablesSlot> getEquippedWearables(WearablesSlotType slotType); | |
| /** | |
| * Returns this entity's supported regions, such as | |
| * {@link WearablesRegion#CHEST} or {@link WearablesRegion#BACK}. | |
| * | |
| * @return A collection of regions into which Wearables may be worn for this entity, | |
| * or an empty collection if none or the entity doesn't support Wearables. | |
| */ | |
| public Collection<WearablesRegion> getSupportedWearablesRegions(); | |
| /** | |
| * Returns this entity's supported {@link WearablesSlotType WearablesSlotTypes}, | |
| * such as {@link WearablesSlotType#CHESTPLATE}. | |
| * | |
| * @return A collection of slot types for any region into which Wearables may be worn for | |
| * this entity, or an empty collection if none or the entity doesn't support Wearables. | |
| */ | |
| public Collection<WearablesSlotType> getSupportedWearablesSlotTypes(); | |
| /** | |
| * Returns this entity's supported {@link WearablesSlotType WearablesSlotTypes} | |
| * for the specified region, such as {@link WearablesSlotType#CHESTPLATE}. | |
| * | |
| * @throws IllegalArgumentException Thrown if region is {@code null}. | |
| * @return A collection of slot types for the specified region into which | |
| * Wearables may be worn for this entity, or an empty collection if | |
| * none, the entity doesn't support Wearables or the specified region. | |
| */ | |
| public Collection<WearablesSlotType> getSupportedWearablesSlotTypes(WearablesRegion region); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot IWearablesSlots} | |
| * this entity supports. Always includes Vanilla armor slots. | |
| * | |
| * @return A collection of slots supported by this entity, or | |
| * an empty stream if it doesn't support Wearables. | |
| */ | |
| public Collection<IWearablesSlot> getSupportedWearablesSlots(); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot IWearablesSlots} in the specified | |
| * region which this entity supports. Always includes Vanilla armor slots. | |
| * <p> | |
| * You may use {@code getSupportedRegions().contains(region)} to test | |
| * whether the specified region is supported for this entity, first. | |
| * | |
| * @throws IllegalArgumentException Thrown if region is {@code null}. | |
| * @return A collection of slots supported by this entity, or an empty stream if | |
| * it doesn't support Wearables at all, or just not the specified region. | |
| */ | |
| public Collection<IWearablesSlot> getSupportedWearableSlots(WearablesRegion region); | |
| /** | |
| * Returns a stream of {@link IWearablesSlot IWearablesSlots} of the specified slot types | |
| * which this entity supports. Always returns {@link IWearablesSlotType#getSlotCount} slots. | |
| * <p> | |
| * You may use {@code getSupportedWearablesSlotTypes().contains(slotType)} to | |
| * test whether the specified slot type is supported for this entity, first. | |
| * | |
| * @throws IllegalArgumentException Thrown if region is {@code null}. | |
| * @return A stream of slots supported by this entity, or an empty stream if it | |
| * doesn't support Wearables at all, or just not the specified slot type. | |
| */ | |
| public Collection<IWearablesSlot> getSupportedWearableSlots(WearablesSlotType slotType); | |
| /** | |
| * Returns a specific {@link IWearablesSlot} of the specified slot type and index. | |
| * | |
| * @param slotType The slot type of which to get a slot from. | |
| * @param index The slot type relative index of which to get a slot from. | |
| * @param force If {@code true}, may return an invalid slot. See {@link IWearablesSlot#isValid}. | |
| * If {@code false}, instead throws an exception in those cases. | |
| * | |
| * @throws IllegalArgumentException Thrown if slotType is {@code null} or index is negative. | |
| * @throws UnsupportedOperationException Thrown if this entity doesn't support Wearables. | |
| * You may test this using {@link IWearablesEntity#is}. | |
| * If force is {@code false}, also thrown if slotType or index is not supported. | |
| * @return A slot for this entity which may be used to get and set the worn Wearable in it. | |
| */ | |
| public IWearablesSlot getWearablesSlot(WearablesSlotType slotType, int index, boolean force); | |
| /** | |
| * Returns a specific {@link IWearablesSlot} of the specified slot type and index. | |
| * | |
| * @param slotType The slot type of which to get a slot from. | |
| * @param index The slot type relative index of which to get a slot from. | |
| * | |
| * @throws IllegalArgumentException Thrown if slotType is {@code null} or index is negative. | |
| * @throws UnsupportedOperationException Thrown if this entity doesn't support Wearables. | |
| * You may test this using {@link IWearablesEntity#is}. | |
| * Also thrown if slotType or index is not supported. | |
| * @return A slot for this entity which may be used to get and set the worn Wearable in it. | |
| */ | |
| public default IWearablesSlot getWearablesSlot(WearablesSlotType slotType, int index) | |
| { return getWearablesSlot(slotType, index, false); } | |
| /** | |
| * Returns a specific {@link IWearablesSlot} that is associated with the specified Vanilla armor {@link EquipmentSlot}. | |
| * | |
| * @param slot The Vanilla equipment slot which to get an associated Wearables slot for. | |
| * @param force If {@code true}, may return an invalid slot. See {@link IWearablesSlot#isValid}. | |
| * If {@code false}, instead throws an exception in those cases. | |
| * | |
| * @throws IllegalArgumentException Thrown if slot is {@code null} or its type is not {@link EquipmentSlot.Type#ARMOR}. | |
| * @throws UnsupportedOperationException Thrown if this entity doesn't support Wearables. | |
| * You may test this using {@link IWearablesEntity#is}. | |
| * If force is {@code false}, also thrown if the {@link WearablesSlotType} | |
| * associated with the specified slot is not supported for this entity. | |
| * @return A slot for this entity which may be used to get and set the worn Wearable in it. | |
| */ | |
| public default IWearablesSlot getWearablesSlot(EquipmentSlot slot, boolean force) | |
| { return getWearablesSlot(WearablesSlotType.fromVanillaSlot(slot), 0, force); } | |
| /** | |
| * Returns a specific {@link IWearablesSlot} that is associated with the specified Vanilla armor {@link EquipmentSlot}. | |
| * | |
| * @param slot The Vanilla equipment slot which to get an associated Wearables slot for. | |
| * | |
| * @throws IllegalArgumentException Thrown if slot is {@code null} or its type is not {@link EquipmentSlot.Type#ARMOR}. | |
| * @throws UnsupportedOperationException Thrown if this entity doesn't support Wearables. | |
| * You may test this using {@link IWearablesEntity#is}. | |
| * Also thrown if the {@link IWearablesSlotType} associated | |
| * with the specified slot is not supported for this entity. | |
| * @return A slot for this entity which may be used to get and set the worn Wearable in it. | |
| */ | |
| public default IWearablesSlot getWearablesSlot(EquipmentSlot slot) | |
| { return getWearablesSlot(slot, false); } | |
| public static final IWearablesEntity DUMMY = new IWearablesEntity() | |
| { | |
| @Override public boolean hasWearables() { return false; } | |
| @Override public Stream<IWearablesSlot> getEquippedWearables() { return Stream.empty(); } | |
| @Override public Stream<IWearablesSlot> getEquippedWearables(WearablesRegion region) { return Stream.empty(); } | |
| @Override public Stream<IWearablesSlot> getEquippedWearables(WearablesSlotType slotType) { return Stream.empty(); } | |
| @Override public Collection<WearablesRegion> getSupportedWearablesRegions() { return Collections.emptyList(); } | |
| @Override public Collection<WearablesSlotType> getSupportedWearablesSlotTypes() { return Collections.emptyList(); } | |
| @Override public Collection<WearablesSlotType> getSupportedWearablesSlotTypes(WearablesRegion region) { return Collections.emptyList(); } | |
| @Override public Collection<IWearablesSlot> getSupportedWearablesSlots() { return Collections.emptyList(); } | |
| @Override public Collection<IWearablesSlot> getSupportedWearableSlots(WearablesRegion region) { return Collections.emptyList(); } | |
| @Override public Collection<IWearablesSlot> getSupportedWearableSlots(WearablesSlotType slotType) { return Collections.emptyList(); } | |
| @Override public IWearablesSlot getWearablesSlot(WearablesSlotType slotType, int index, boolean force) | |
| { throw new UnsupportedOperationException("Entity doesn't support Wearables"); } | |
| }; | |
| /** Returns whether the specified entity may have Wearables equipped. */ | |
| public static boolean is(Entity entity) | |
| { return (entity instanceof IWearablesEntity); } | |
| /** Returns the specified {@link Entity} casted as | |
| * {@link IWearablesEntity}, or {@link #DUMMY} if it isn't. */ | |
| public static IWearablesEntity from(Entity entity) | |
| { return is(entity) ? (IWearablesEntity)entity : DUMMY; } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.mcft.copy.wearables.api; | |
| import net.minecraft.entity.player.PlayerEntity; | |
| import net.minecraft.item.Item; | |
| import net.minecraft.item.ItemStack; | |
| /** | |
| * Implemented by {@link Item Items} to add additional | |
| * control over equipment rules and a tick handler. | |
| */ | |
| public interface IWearablesItem | |
| { | |
| /** | |
| * Returns whether the specified {@link ItemStack} can be equipped in the | |
| * specified {@link IWearablesSlot} by the entity. This doesn't necessarily | |
| * prevent an item from being equipped through other means. | |
| * <p> | |
| * Note that which slots an item can be equipped into is controlled by | |
| * configuration stored in data packs under {@code config/wearables/items/} | |
| * and not just this method. | |
| * | |
| * @param slot The slot to be checked. | |
| * Use {@link IWearablesSlot#getEntity()} to access the entity | |
| * (not guaranteed to be a {@link PlayerEntity}). | |
| * @param stack The exact stack to be checked whether it can be equipped. | |
| */ | |
| public default boolean canEquip(IWearablesSlot slot, ItemStack stack) { return true; } | |
| /** | |
| * Returns whether this item can be unequipped from the specified | |
| * {@link IWearablesSlot} by the entity. This doesn't necessarily prevent | |
| * the item from being unequipped through other means, such as death. | |
| * | |
| * @param slot The slot to be checked. | |
| * Use {@link IWearablesSlot#get()} to get the item to be | |
| * checked whether it can be unequipped. | |
| * Use {@link IWearablesSlot#getEntity()} to access the entity | |
| * (not guaranteed to be a {@link PlayerEntity}). | |
| */ | |
| public default boolean canUnequip(IWearablesSlot slot) { return true; } | |
| /** | |
| * Called right after this item is equipped in the specified {@link IWearablesSlot}, | |
| * both on the client (which isn't authoritative) and the server. | |
| * | |
| * @param slot The slot the item got equipped to. | |
| * Use {@link IWearablesSlot#get()} to get the full | |
| * {@link ItemStack} that got equipped. | |
| * Use {@link IWearablesSlot#getEntity()} to access the entity | |
| * (not guaranteed to be a {@link PlayerEntity}). | |
| */ | |
| public default void onEquip(IWearablesSlot slot) { } | |
| /** | |
| * Called right before this item is unequipped from the specified | |
| * {@link IWearablesSlot}. Also called when the item is dropped / broken, | |
| * due to death or taking damage. | |
| * | |
| * @param slot The slot the item is being uneqipped from. | |
| * Use {@link IWearablesSlot#get()} to get the full | |
| * {@link ItemStack} about to be unequipped. | |
| * Use {@link IWearablesSlot#getEntity()} to access the entity | |
| * (not guaranteed to be a {@link PlayerEntity}). | |
| */ | |
| public default void onUnequip(IWearablesSlot slot) { } | |
| /** Returns whether {@link #onEquippedTick} should be called for this item. */ | |
| public default boolean doesTick() { return false; } | |
| /** | |
| * Called each tick while this item is equipped in the specified {@link IWearablesSlot}. | |
| * Only called if {@link #doesTick()} returns true (false by default!). | |
| * | |
| * @param slot The slot the item is currently equipped in. | |
| * Use {@link IWearablesSlot#get()} to get the full | |
| * {@link ItemStack} that is currently equipped. | |
| * Use {@link IWearablesSlot#getEntity()} to access the entity | |
| * (not guaranteed to be a {@link PlayerEntity}). | |
| * @param equippedTime The number of ticks (usually 20th of a second) since | |
| * the item got equipped. Can be used to run code only | |
| * every number of ticks: <p> | |
| * {@code if ((equippedTime % 20) != 0) return;} | |
| */ | |
| public default void onEquippedTick(IWearablesSlot slot, int equippedTime) { } | |
| public static final IWearablesItem DUMMY = new IWearablesItem(){}; | |
| /** Returns the specified {@link Item} casted as | |
| * {@link IWearablesItem}, or {@link #DUMMY} if it isn't. */ | |
| public static IWearablesItem from(Item item) | |
| { return (item instanceof IWearablesItem) ? (IWearablesItem)item : DUMMY; } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.mcft.copy.wearables.api; | |
| import net.minecraft.enchantment.EnchantmentHelper; | |
| import net.minecraft.entity.Entity; | |
| import net.minecraft.entity.player.PlayerEntity; | |
| import net.minecraft.item.ItemStack; | |
| /** Interface for a specific slot on an entity into which a wearable item may be equipped. */ | |
| public interface IWearablesSlot | |
| { | |
| /** Gets the entity this slot is attached to. */ | |
| public Entity getEntity(); | |
| /** Gets the {@link WearablesSlotType} of this slot. */ | |
| public WearablesSlotType getSlotType(); | |
| /** Gets the numeric index of the slot, differenciating it from other slots of the same type. */ | |
| public int getIndex(); | |
| /** Gets the {@link WearablesRegion} of this slot. */ | |
| public default WearablesRegion getRegion() | |
| { return getSlotType().getRegion(); } | |
| /** | |
| * Gets the "order" of this slot, whether it's "above" or "below" other slots. | |
| * Smaller number is above, larger is below. Always 0 for Vanilla slots. Default is 500. | |
| * <p> | |
| * TODO: Eventually, order may change on the fly. For example, an amulet could be worn above or below the chestplate. | |
| */ | |
| public int getOrder(); | |
| /** | |
| * Gets whether this slot is valid / supported by this entity. | |
| * <p> | |
| * This is {@code false} if the slot type is not supported or the index is | |
| * equal to or larger than {@link IWearablesSlotType#getSlotCount}, which | |
| * can happen if the item is worn in a slot that is later removed from the | |
| * data pack or its count changed. Invalid slots might be returned from | |
| * {@link IWearablesEntity#getEquippedWearables getEquippedWearables} or | |
| * {@link IWearablesEntity#getWearablesSlot getWearablesSlot(..., true)}. | |
| * <p> | |
| * An invalid slot still allows setting the stack, but | |
| * it should only ever be set to {@link ItemStack#EMPTY}. | |
| */ | |
| public default boolean isValid() | |
| { return IWearablesEntity.from(getEntity()).getSupportedWearablesSlots().contains(this); } | |
| /** Gets the {@link ItemStack} currently contained | |
| * in this slot, or {@link ItemStack#EMPTY} if empty. */ | |
| public ItemStack get(); | |
| /** Sets the {@link ItemStack} contained in this slot, without | |
| * checking if the specified stack is valid or can be equipped. */ | |
| public void set(ItemStack value); | |
| /** | |
| * Returns if the specified {@link ItemStack} can be equipped in this slot. | |
| * Does not check if a currently equipped stack can be unequipped. | |
| * Returns {@code true} if the specified stack is {@code null}. | |
| */ | |
| public default boolean canEquip(ItemStack stack) | |
| { return stack.isEmpty() || (isValid() && IWearablesItem.from(stack.getItem()).canEquip(this, stack)); } | |
| /** Returns if the {@link ItemStack} currently contained in this slot can | |
| * be unequipped. Returns {@code true} if the current stack is {@code null}. */ | |
| public default boolean canUnequip() | |
| { | |
| return get().isEmpty() || | |
| ((getEntity() instanceof PlayerEntity) && ((PlayerEntity)getEntity()).isCreative()) || | |
| (!EnchantmentHelper.hasBindingCurse(get()) && IWearablesItem.from(get().getItem()).canUnequip(this)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.mcft.copy.wearables.api; | |
| import java.util.regex.Pattern; | |
| public final class WearablesRegion | |
| { | |
| public static final Pattern REGION_REGEX | |
| = Pattern.compile("[a-z]+"); | |
| public static final WearablesRegion HEAD = new WearablesRegion("head"); | |
| public static final WearablesRegion CHEST = new WearablesRegion("chest"); | |
| public static final WearablesRegion LEGS = new WearablesRegion("legs"); | |
| public static final WearablesRegion FEET = new WearablesRegion("feet"); | |
| public static final WearablesRegion BACK = new WearablesRegion("back"); | |
| public static final WearablesRegion ARMS = new WearablesRegion("arms"); | |
| public final String name; | |
| public WearablesRegion(String name) | |
| { | |
| if ((name == null) || name.isEmpty()) | |
| throw new IllegalArgumentException("name is null or empty"); | |
| if (!REGION_REGEX.matcher(name).matches()) | |
| throw new IllegalArgumentException("name '" + name + "' is not a valid slot type string"); | |
| this.name = name; | |
| } | |
| @Override | |
| public String toString() | |
| { return name; } | |
| @Override | |
| public boolean equals(Object obj) | |
| { | |
| return (obj instanceof WearablesRegion) | |
| && ((WearablesRegion)obj).name.equals(this.name); | |
| } | |
| @Override | |
| public int hashCode() | |
| { return this.name.hashCode(); } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package net.mcft.copy.wearables.api; | |
| import java.util.regex.Pattern; | |
| import net.minecraft.entity.EquipmentSlot; | |
| public final class WearablesSlotType | |
| { | |
| public static final Pattern SLOT_TYPE_REGEX | |
| = Pattern.compile("(?<region>[a-z]+):([a-z]+/)*(?<name>[a-z]+)"); | |
| public static final WearablesSlotType HELMET = new WearablesSlotType( "head:armor/helmet" ); | |
| public static final WearablesSlotType CHESTPLATE = new WearablesSlotType("chest:armor/chestplate"); | |
| public static final WearablesSlotType LEGGINGS = new WearablesSlotType( "legs:armor/leggings" ); | |
| public static final WearablesSlotType BOOTS = new WearablesSlotType( "feet:armor/boots" ); | |
| public final String fullName; | |
| public WearablesSlotType(String fullName) | |
| { | |
| if ((fullName == null) || fullName.isEmpty()) | |
| throw new IllegalArgumentException("fullName is null or empty"); | |
| if (!SLOT_TYPE_REGEX.matcher(fullName).matches()) | |
| throw new IllegalArgumentException("fullName '" + fullName + "' is not a valid slot type string"); | |
| this.fullName = fullName; | |
| } | |
| public static WearablesSlotType fromVanillaSlot(EquipmentSlot slot) | |
| { | |
| if (slot == null) | |
| throw new IllegalArgumentException("slot is null"); | |
| if (slot.getType() != EquipmentSlot.Type.ARMOR) | |
| throw new IllegalArgumentException("slot's type is not ARMOR"); | |
| switch (slot) { | |
| case HEAD: return HELMET; | |
| case CHEST: return CHESTPLATE; | |
| case LEGS: return LEGGINGS; | |
| case FEET: return BOOTS; | |
| default: throw new IllegalArgumentException( | |
| "slot is not HEAD, CHEST, LEGS or FEET (how did this happen?)"); | |
| } | |
| } | |
| public WearablesRegion getRegion() | |
| { return new WearablesRegion(this.fullName.substring(0, this.fullName.indexOf(':'))); } | |
| public String getName() | |
| { | |
| int start = this.fullName.lastIndexOf('/'); | |
| if (start < 0) start = this.fullName.indexOf(':'); | |
| return this.fullName.substring(start + 1); | |
| } | |
| // TODO: Is this needed? Is this the proper implementation? | |
| // public CharSequence[] getParts() | |
| // { | |
| // int start = 0; | |
| // int numSeparators = 0; | |
| // for (int i = 0; i < this.fullName.length(); i++) | |
| // switch (this.fullName.charAt(i)) { | |
| // case ':': start = i + 1; break; | |
| // case '/': numSeparators++; break; | |
| // } | |
| // | |
| // int index = 0; | |
| // CharSequence[] parts = new CharSequence[numSeparators + 1]; | |
| // for (int i = start; i < this.fullName.length(); i++) | |
| // if (this.fullName.charAt(i) == '/') { | |
| // parts[index++] = this.fullName.subSequence(start, i); | |
| // start = i + 1; | |
| // } | |
| // parts[index] = this.fullName.subSequence(start, this.fullName.length() - 1); | |
| // return parts; | |
| // } | |
| @Override | |
| public String toString() | |
| { return fullName; } | |
| @Override | |
| public boolean equals(Object obj) | |
| { | |
| return (obj instanceof WearablesSlotType) | |
| && ((WearablesSlotType)obj).fullName.equals(this.fullName); | |
| } | |
| @Override | |
| public int hashCode() | |
| { return this.fullName.hashCode(); } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment