From 8d2b7ea0cbfa6757df0cb6ba6c80009aac024e82 Mon Sep 17 00:00:00 2001 From: LOOHP Date: Fri, 19 Feb 2021 23:33:21 +0800 Subject: [PATCH] 0.4.0 SNAPSHOT --- pom.xml | 2 +- src/main/java/com/loohp/limbo/Console.java | 2 +- .../com/loohp/limbo/Entity/ArmorStand.java | 135 ++++++ .../com/loohp/limbo/Entity/DataWatcher.java | 178 ++++++++ .../java/com/loohp/limbo/Entity/Entity.java | 283 +++++++++++++ .../com/loohp/limbo/Entity/EntityType.java | 395 ++++++++++++++++++ .../com/loohp/limbo/Entity/LivingEntity.java | 132 ++++++ .../java/com/loohp/limbo/Entity/Pose.java | 34 ++ .../loohp/limbo/File/FileConfiguration.java | 57 ++- .../loohp/limbo/File/ServerProperties.java | 61 ++- .../loohp/limbo/Inventory/EquipmentSlot.java | 12 + src/main/java/com/loohp/limbo/Limbo.java | 39 +- .../java/com/loohp/limbo/Player/Player.java | 121 +++++- .../limbo/Player/PlayerInteractManager.java | 135 ++++++ .../java/com/loohp/limbo/Player/Unsafe.java | 8 +- .../loohp/limbo/Plugins/PluginManager.java | 1 - .../loohp/limbo/Server/ClientConnection.java | 82 ++-- ...s.java => PacketPlayOutEntityDestroy.java} | 29 +- .../Packets/PacketPlayOutEntityMetadata.java | 156 +++++++ .../Server/Packets/PacketPlayOutRespawn.java | 3 +- .../Packets/PacketPlayOutSpawnEntity.java | 111 +++++ .../PacketPlayOutSpawnEntityLiving.java | 111 +++++ .../Packets/PacketPlayOutUnloadChunk.java | 37 ++ src/main/java/com/loohp/limbo/Tick.java | 51 +++ src/main/java/com/loohp/limbo/Unsafe.java | 25 ++ .../com/loohp/limbo/Utils/NamespacedKey.java | 6 + .../com/loohp/limbo/Utils/Rotation3f.java | 77 ++++ .../java/com/loohp/limbo/World/Schematic.java | 28 +- .../java/com/loohp/limbo/World/Unsafe.java | 21 + .../java/com/loohp/limbo/World/World.java | 139 ++++++ src/main/resources/mapping.json | 8 +- src/main/resources/server.properties | 6 + 32 files changed, 2345 insertions(+), 140 deletions(-) create mode 100644 src/main/java/com/loohp/limbo/Entity/ArmorStand.java create mode 100644 src/main/java/com/loohp/limbo/Entity/DataWatcher.java create mode 100644 src/main/java/com/loohp/limbo/Entity/Entity.java create mode 100644 src/main/java/com/loohp/limbo/Entity/EntityType.java create mode 100644 src/main/java/com/loohp/limbo/Entity/LivingEntity.java create mode 100644 src/main/java/com/loohp/limbo/Entity/Pose.java create mode 100644 src/main/java/com/loohp/limbo/Inventory/EquipmentSlot.java create mode 100644 src/main/java/com/loohp/limbo/Player/PlayerInteractManager.java rename src/main/java/com/loohp/limbo/Server/Packets/{PacketPlayOutShowPlayerSkins.java => PacketPlayOutEntityDestroy.java} (52%) create mode 100644 src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityMetadata.java create mode 100644 src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntity.java create mode 100644 src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntityLiving.java create mode 100644 src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutUnloadChunk.java create mode 100644 src/main/java/com/loohp/limbo/Tick.java create mode 100644 src/main/java/com/loohp/limbo/Utils/Rotation3f.java create mode 100644 src/main/java/com/loohp/limbo/World/Unsafe.java diff --git a/pom.xml b/pom.xml index 83dae03..6a4551a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.loohp Limbo - 0.3.8-ALPHA + 0.4.0-ALPHA-SNAPSHOT src/main/java diff --git a/src/main/java/com/loohp/limbo/Console.java b/src/main/java/com/loohp/limbo/Console.java index 96d3726..676bc2f 100644 --- a/src/main/java/com/loohp/limbo/Console.java +++ b/src/main/java/com/loohp/limbo/Console.java @@ -152,7 +152,7 @@ public class Console implements CommandSender { return; } while (true) { - String command = tabReader.readLine(PROMPT); + String command = tabReader.readLine(PROMPT).trim(); if (command.length() > 0) { String[] input = CustomStringUtils.splitStringToArgs(command); new Thread(() -> Limbo.getInstance().dispatchCommand(this, input)).start(); diff --git a/src/main/java/com/loohp/limbo/Entity/ArmorStand.java b/src/main/java/com/loohp/limbo/Entity/ArmorStand.java new file mode 100644 index 0000000..e13204d --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/ArmorStand.java @@ -0,0 +1,135 @@ +package com.loohp.limbo.Entity; + +import java.util.UUID; + +import com.loohp.limbo.Limbo; +import com.loohp.limbo.Entity.DataWatcher.WatchableField; +import com.loohp.limbo.Entity.DataWatcher.WatchableObjectType; +import com.loohp.limbo.Location.Location; +import com.loohp.limbo.Utils.Rotation3f; +import com.loohp.limbo.World.World; + +public class ArmorStand extends LivingEntity { + + @WatchableField(MetadataIndex = 14, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x01) + protected boolean small = false; + @WatchableField(MetadataIndex = 14, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x04) + protected boolean arms = false; + @WatchableField(MetadataIndex = 14, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x08) + protected boolean noBasePlate = false; + @WatchableField(MetadataIndex = 14, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x10) + protected boolean marker = false; + @WatchableField(MetadataIndex = 15, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f headRotation = new Rotation3f(0.0, 0.0, 0.0); + @WatchableField(MetadataIndex = 16, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f bodyRotation = new Rotation3f(0.0, 0.0, 0.0); + @WatchableField(MetadataIndex = 17, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f leftArmRotation = new Rotation3f(-10.0, 0.0, -10.0); + @WatchableField(MetadataIndex = 18, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f rightArmRotation = new Rotation3f(-15.0, 0.0, 10.0); + @WatchableField(MetadataIndex = 19, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f leftLegRotation = new Rotation3f(-1.0, 0.0, -1.0); + @WatchableField(MetadataIndex = 20, WatchableObjectType = WatchableObjectType.ROTATION) + protected Rotation3f rightLegRotation = new Rotation3f(1.0, 0.0, 1.0); + + public ArmorStand(int entityId, UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + super(EntityType.ARMOR_STAND, entityId, uuid, world, x, y, z, yaw, pitch); + } + + public ArmorStand(UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + this(Limbo.getInstance().getNextEntityId(), uuid, world, x, y, z, yaw, pitch); + } + + public ArmorStand(World world, double x, double y, double z, float yaw, float pitch) { + this(Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), world, x, y, z, yaw, pitch); + } + + public ArmorStand(UUID uuid, Location location) { + this(Limbo.getInstance().getNextEntityId(), uuid, location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public ArmorStand(Location location) { + this(Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public boolean isSmall() { + return small; + } + + public void setSmall(boolean small) { + this.small = small; + } + + public boolean showArms() { + return arms; + } + + public void setArms(boolean arms) { + this.arms = arms; + } + + public boolean hasNoBasePlate() { + return noBasePlate; + } + + public void setNoBasePlate(boolean noBasePlate) { + this.noBasePlate = noBasePlate; + } + + public boolean isMarker() { + return marker; + } + + public void setMarker(boolean marker) { + this.marker = marker; + } + + public Rotation3f getHeadRotation() { + return headRotation; + } + + public void setHeadRotation(Rotation3f headRotation) { + this.headRotation = headRotation; + } + + public Rotation3f getBodyRotation() { + return bodyRotation; + } + + public void setBodyRotation(Rotation3f bodyRotation) { + this.bodyRotation = bodyRotation; + } + + public Rotation3f getLeftArmRotation() { + return leftArmRotation; + } + + public void setLeftArmRotation(Rotation3f leftArmRotation) { + this.leftArmRotation = leftArmRotation; + } + + public Rotation3f getRightArmRotation() { + return rightArmRotation; + } + + public void setRightArmRotation(Rotation3f rightArmRotation) { + this.rightArmRotation = rightArmRotation; + } + + public Rotation3f getLeftLegRotation() { + return leftLegRotation; + } + + public void setLeftLegRotation(Rotation3f leftLegRotation) { + this.leftLegRotation = leftLegRotation; + } + + public Rotation3f getRightLegRotation() { + return rightLegRotation; + } + + public void setRightLegRotation(Rotation3f rightLegRotation) { + this.rightLegRotation = rightLegRotation; + } + +} diff --git a/src/main/java/com/loohp/limbo/Entity/DataWatcher.java b/src/main/java/com/loohp/limbo/Entity/DataWatcher.java new file mode 100644 index 0000000..cebd7eb --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/DataWatcher.java @@ -0,0 +1,178 @@ +package com.loohp.limbo.Entity; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class DataWatcher { + + private Entity entity; + private Map values; + + public DataWatcher(Entity entity) { + this.entity = entity; + this.values = new HashMap<>(); + + Class clazz = entity.getClass(); + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + WatchableField a = field.getAnnotation(WatchableField.class); + if (a != null) { + field.setAccessible(true); + try { + values.put(field, new WatchableObject(field.get(entity), a.MetadataIndex(), a.WatchableObjectType(), a.IsOptional(), a.IsBitmask(), a.Bitmask())); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + clazz = clazz.getSuperclass(); + } + } + + public Entity getEntity() { + return entity; + } + + public boolean isValid() { + return entity.isValid(); + } + + public synchronized Map update() throws IllegalArgumentException, IllegalAccessException { + if (!isValid()) { + return null; + } + Map updated = new HashMap<>(); + for (Entry entry : values.entrySet()) { + Field field = entry.getKey(); + WatchableObject watchableObj = entry.getValue(); + field.setAccessible(true); + Object newValue = field.get(entity); + if ((newValue == null && watchableObj.getValue() != null) || (newValue != null && watchableObj.getValue() == null) || (newValue != null && watchableObj.getValue() != null && !newValue.equals(watchableObj.getValue()))) { + watchableObj.setValue(newValue); + updated.put(field, watchableObj); + } + } + return updated; + } + + public Map getWatchableObjects() { + return Collections.unmodifiableMap(values); + } + + public static class WatchableObject { + + private int index; + private WatchableObjectType type; + private boolean optional; + private boolean isBitmask; + private int bitmask; + + private Object value; + + public WatchableObject(Object value, int index, WatchableObjectType type, boolean optional, boolean isBitmask, int bitmask) { + this.index = index; + this.type = type; + this.optional = optional; + this.isBitmask = isBitmask; + this.bitmask = bitmask; + this.value = value; + } + + public WatchableObject(Object value, int index, WatchableObjectType type, boolean isBitmask, int bitmask) { + this(value, index, type, false, isBitmask, bitmask); + } + + public WatchableObject(Object value, int index, WatchableObjectType type, boolean optional) { + this(value, index, type, optional, false, 0x00); + } + + public WatchableObject(Object value, int index, WatchableObjectType type) { + this(value, index, type, false, false, 0x00); + } + + public Object getValue() { + return value; + } + + public void setValue(Object newValue) { + this.value = newValue; + } + + public int getIndex() { + return index; + } + + public WatchableObjectType getType() { + return type; + } + + public boolean isOptional() { + return optional; + } + + public boolean isBitmask() { + return isBitmask; + } + + public int getBitmask() { + return bitmask; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public static @interface WatchableField { + int MetadataIndex(); + WatchableObjectType WatchableObjectType(); + boolean IsOptional() default false; + boolean IsBitmask() default false; + int Bitmask() default 0x00; + } + + public static enum WatchableObjectType { + BYTE(0), + VARINT(1, 17), + FLOAT(2), + STRING(3), + CHAT(4, 5), + SLOT(6), + BOOLEAN(7), + ROTATION(8), + POSITION(9, 10), + DIRECTION(11), + UUID(-1, 12), + BLOCKID(-1, 13), + NBT(14), + PARTICLE(15), + VILLAGER_DATA(16), + POSE(18); + + int typeId; + int optionalTypeId; + + WatchableObjectType(int typeId, int optionalTypeId) { + this.typeId = typeId; + this.optionalTypeId = optionalTypeId; + } + + WatchableObjectType(int typeId) { + this(typeId, -1); + } + + public int getTypeId() { + return typeId; + } + + public int getOptionalTypeId() { + return optionalTypeId; + } + } + +} diff --git a/src/main/java/com/loohp/limbo/Entity/Entity.java b/src/main/java/com/loohp/limbo/Entity/Entity.java new file mode 100644 index 0000000..3181da7 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/Entity.java @@ -0,0 +1,283 @@ +package com.loohp.limbo.Entity; + +import java.util.UUID; + +import com.loohp.limbo.Limbo; +import com.loohp.limbo.Entity.DataWatcher.WatchableField; +import com.loohp.limbo.Entity.DataWatcher.WatchableObjectType; +import com.loohp.limbo.Location.Location; +import com.loohp.limbo.World.World; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +public abstract class Entity { + + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x01) + protected boolean onFire = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x02) + protected boolean crouching = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x04) + protected boolean unused = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x08) + protected boolean sprinting = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x10) + protected boolean swimming = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x20) + protected boolean invisible = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x40) + protected boolean glowing = false; + @WatchableField(MetadataIndex = 0, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x80) + protected boolean elytraFlying = false; + @WatchableField(MetadataIndex = 1, WatchableObjectType = WatchableObjectType.VARINT) + protected int air = 300; + @WatchableField(MetadataIndex = 2, WatchableObjectType = WatchableObjectType.CHAT, IsOptional = true) + protected BaseComponent[] customName = null; + @WatchableField(MetadataIndex = 3, WatchableObjectType = WatchableObjectType.BOOLEAN) + protected boolean customNameVisible = false; + @WatchableField(MetadataIndex = 4, WatchableObjectType = WatchableObjectType.BOOLEAN) + protected boolean silent = false; + @WatchableField(MetadataIndex = 5, WatchableObjectType = WatchableObjectType.BOOLEAN) + protected boolean noGravity = false; + @WatchableField(MetadataIndex = 6, WatchableObjectType = WatchableObjectType.POSE) + protected Pose pose = Pose.STANDING; + + protected final EntityType type; + + protected int entityId; + protected UUID uuid; + protected World world; + protected double x; + protected double y; + protected double z; + protected float yaw; + protected float pitch; + + public Entity(EntityType type, int entityId, UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + this.type = type; + this.entityId = entityId; + this.uuid = uuid; + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + public Entity(EntityType type, UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + this(type, Limbo.getInstance().getNextEntityId(), uuid, world, x, y, z, yaw, pitch); + } + + public Entity(EntityType type, World world, double x, double y, double z, float yaw, float pitch) { + this(type, Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), world, x, y, z, yaw, pitch); + } + + public Entity(EntityType type, UUID uuid, Location location) { + this(type, Limbo.getInstance().getNextEntityId(), uuid, location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public Entity(EntityType type, Location location) { + this(type, Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public EntityType getType() { + return type; + } + + public Location getLocation() { + return new Location(world, x, y, z, yaw, pitch); + } + + public void teleport(Location location) { + this.world = location.getWorld(); + this.x = location.getX(); + this.y = location.getY(); + this.z = location.getZ(); + this.yaw = location.getYaw(); + this.pitch = location.getPitch(); + } + + public BaseComponent[] getCustomName() { + return customName; + } + + public void setCustomName(String name) { + this.customName = name == null ? null : new BaseComponent[] {new TextComponent(name)}; + } + + public void setCustomName(BaseComponent component) { + this.customName = component == null ? null : new BaseComponent[] {component}; + } + + public void setCustomName(BaseComponent[] components) { + this.customName = components; + } + + public boolean isOnFire() { + return onFire; + } + + public void setOnFire(boolean onFire) { + this.onFire = onFire; + } + + public boolean isCrouching() { + return crouching; + } + + public void setCrouching(boolean crouching) { + this.crouching = crouching; + } + + public boolean isSprinting() { + return sprinting; + } + + public void setSprinting(boolean sprinting) { + this.sprinting = sprinting; + } + + public boolean isSwimming() { + return swimming; + } + + public void setSwimming(boolean swimming) { + this.swimming = swimming; + } + + public boolean isInvisible() { + return invisible; + } + + public void setInvisible(boolean invisible) { + this.invisible = invisible; + } + + public boolean isGlowing() { + return glowing; + } + + public void setGlowing(boolean glowing) { + this.glowing = glowing; + } + + public boolean isElytraFlying() { + return elytraFlying; + } + + public void setElytraFlying(boolean elytraFlying) { + this.elytraFlying = elytraFlying; + } + + public int getAir() { + return air; + } + + public void setAir(int air) { + this.air = air; + } + + public boolean isCustomNameVisible() { + return customNameVisible; + } + + public void setCustomNameVisible(boolean customNameVisible) { + this.customNameVisible = customNameVisible; + } + + public boolean isSilent() { + return silent; + } + + public void setSilent(boolean silent) { + this.silent = silent; + } + + public boolean hasGravity() { + return !noGravity; + } + + public void setGravity(boolean gravity) { + this.noGravity = !gravity; + } + + public Pose getPose() { + return pose; + } + + public void setPose(Pose pose) { + this.pose = pose; + } + + public World getWorld() { + return world; + } + + public void setWorld(World world) { + this.world = world; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + public float getYaw() { + return yaw; + } + + public void setYaw(float yaw) { + this.yaw = yaw; + } + + public float getPitch() { + return pitch; + } + + public void setPitch(float pitch) { + this.pitch = pitch; + } + + public int getEntityId() { + return entityId; + } + + public UUID getUniqueId() { + return uuid; + } + + public boolean isValid() { + return world.getEntities().contains(this); + } + + @SuppressWarnings("deprecation") + public void remove() { + Limbo.getInstance().getUnsafe().removeEntity(world, this); + } + + @SuppressWarnings("deprecation") + public DataWatcher getDataWatcher() { + return Limbo.getInstance().getUnsafe().getDataWatcher(world, this); + } + +} diff --git a/src/main/java/com/loohp/limbo/Entity/EntityType.java b/src/main/java/com/loohp/limbo/Entity/EntityType.java new file mode 100644 index 0000000..9066a38 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/EntityType.java @@ -0,0 +1,395 @@ +package com.loohp.limbo.Entity; + +import java.util.HashMap; +import java.util.Map; + +import com.loohp.limbo.Utils.NamespacedKey; +import com.loohp.limbo.Player.Player; + +public enum EntityType { + + // These strings MUST match the strings in nms.EntityTypes and are case sensitive. + /** + * An item resting on the ground. + *

+ * Spawn with {@link World#dropItem(Location, ItemStack)} or {@link + * World#dropItemNaturally(Location, ItemStack)} + */ + //DROPPED_ITEM("item", Item.class, 1, false), + /** + * An experience orb. + */ + //EXPERIENCE_ORB("experience_orb", ExperienceOrb.class, 2), + /** + * @see AreaEffectCloud + */ + //AREA_EFFECT_CLOUD("area_effect_cloud", AreaEffectCloud.class, 3), + /** + * @see ElderGuardian + */ + //ELDER_GUARDIAN("elder_guardian", ElderGuardian.class, 4), + /** + * @see WitherSkeleton + */ + //WITHER_SKELETON("wither_skeleton", WitherSkeleton.class, 5), + /** + * @see Stray + */ + //STRAY("stray", Stray.class, 6), + /** + * A flying chicken egg. + */ + //EGG("egg", Egg.class, 7), + /** + * A leash attached to a fencepost. + */ + //LEASH_HITCH("leash_knot", LeashHitch.class, 8), + /** + * A painting on a wall. + */ + //PAINTING("painting", Painting.class, 9), + /** + * An arrow projectile; may get stuck in the ground. + */ + //ARROW("arrow", Arrow.class, 10), + /** + * A flying snowball. + */ + //SNOWBALL("snowball", Snowball.class, 11), + /** + * A flying large fireball, as thrown by a Ghast for example. + */ + //FIREBALL("fireball", LargeFireball.class, 12), + /** + * A flying small fireball, such as thrown by a Blaze or player. + */ + //SMALL_FIREBALL("small_fireball", SmallFireball.class, 13), + /** + * A flying ender pearl. + */ + //ENDER_PEARL("ender_pearl", EnderPearl.class, 14), + /** + * An ender eye signal. + */ + //ENDER_SIGNAL("eye_of_ender", EnderSignal.class, 15), + /** + * A flying splash potion. + */ + //SPLASH_POTION("potion", ThrownPotion.class, 16, false), + /** + * A flying experience bottle. + */ + //THROWN_EXP_BOTTLE("experience_bottle", ThrownExpBottle.class, 17), + /** + * An item frame on a wall. + */ + //ITEM_FRAME("item_frame", ItemFrame.class, 18), + /** + * A flying wither skull projectile. + */ + //WITHER_SKULL("wither_skull", WitherSkull.class, 19), + /** + * Primed TNT that is about to explode. + */ + //PRIMED_TNT("tnt", TNTPrimed.class, 20), + /** + * A block that is going to or is about to fall. + */ + //FALLING_BLOCK("falling_block", FallingBlock.class, 21, false), + /** + * Internal representation of a Firework once it has been launched. + */ + //FIREWORK("firework_rocket", Firework.class, 22, false), + /** + * @see Husk + */ + //HUSK("husk", Husk.class, 23), + /** + * Like {@link #ARROW} but causes the {@link PotionEffectType#GLOWING} effect on all team members. + */ + //SPECTRAL_ARROW("spectral_arrow", SpectralArrow.class, 24), + /** + * Bullet fired by {@link #SHULKER}. + */ + //SHULKER_BULLET("shulker_bullet", ShulkerBullet.class, 25), + /** + * Like {@link #FIREBALL} but with added effects. + */ + //DRAGON_FIREBALL("dragon_fireball", DragonFireball.class, 26), + /** + * @see ZombieVillager + */ + //ZOMBIE_VILLAGER("zombie_villager", ZombieVillager.class, 27), + /** + * @see SkeletonHorse + */ + //SKELETON_HORSE("skeleton_horse", SkeletonHorse.class, 28), + /** + * @see ZombieHorse + */ + //ZOMBIE_HORSE("zombie_horse", ZombieHorse.class, 29), + /** + * Mechanical entity with an inventory for placing weapons / armor into. + */ + ARMOR_STAND("armor_stand", ArmorStand.class, 1), + /** + * @see Donkey + */ + //DONKEY("donkey", Donkey.class, 31), + /** + * @see Mule + */ + //MULE("mule", Mule.class, 32), + /** + * @see EvokerFangs + */ + //EVOKER_FANGS("evoker_fangs", EvokerFangs.class, 33), + /** + * @see Evoker + */ + //EVOKER("evoker", Evoker.class, 34), + /** + * @see Vex + */ + //VEX("vex", Vex.class, 35), + /** + * @see Vindicator + */ + //VINDICATOR("vindicator", Vindicator.class, 36), + /** + * @see Illusioner + */ + //ILLUSIONER("illusioner", Illusioner.class, 37), + /** + * @see CommandMinecart + */ + //MINECART_COMMAND("command_block_minecart", CommandMinecart.class, 40), + /** + * A placed boat. + */ + //BOAT("boat", Boat.class, 41), + /** + * @see RideableMinecart + */ + //MINECART("minecart", RideableMinecart.class, 42), + /** + * @see StorageMinecart + */ + //MINECART_CHEST("chest_minecart", StorageMinecart.class, 43), + /** + * @see PoweredMinecart + */ + //MINECART_FURNACE("furnace_minecart", PoweredMinecart.class, 44), + /** + * @see ExplosiveMinecart + */ + //MINECART_TNT("tnt_minecart", ExplosiveMinecart.class, 45), + /** + * @see HopperMinecart + */ + //MINECART_HOPPER("hopper_minecart", HopperMinecart.class, 46), + /** + * @see SpawnerMinecart + */ + //MINECART_MOB_SPAWNER("spawner_minecart", SpawnerMinecart.class, 47), + //CREEPER("creeper", Creeper.class, 50), + //SKELETON("skeleton", Skeleton.class, 51), + //SPIDER("spider", Spider.class, 52), + //GIANT("giant", Giant.class, 53), + //ZOMBIE("zombie", Zombie.class, 54), + //SLIME("slime", Slime.class, 55), + //GHAST("ghast", Ghast.class, 56), + //ZOMBIFIED_PIGLIN("zombified_piglin", PigZombie.class, 57), + //ENDERMAN("enderman", Enderman.class, 58), + //CAVE_SPIDER("cave_spider", CaveSpider.class, 59), + //SILVERFISH("silverfish", Silverfish.class, 60), + //BLAZE("blaze", Blaze.class, 61), + //MAGMA_CUBE("magma_cube", MagmaCube.class, 62), + //ENDER_DRAGON("ender_dragon", EnderDragon.class, 63), + //WITHER("wither", Wither.class, 64), + //BAT("bat", Bat.class, 65), + //WITCH("witch", Witch.class, 66), + //ENDERMITE("endermite", Endermite.class, 67), + //GUARDIAN("guardian", Guardian.class, 68), + //SHULKER("shulker", Shulker.class, 69), + //PIG("pig", Pig.class, 90), + //SHEEP("sheep", Sheep.class, 91), + //COW("cow", Cow.class, 92), + //CHICKEN("chicken", Chicken.class, 93), + //SQUID("squid", Squid.class, 94), + //WOLF("wolf", Wolf.class, 95), + //MUSHROOM_COW("mooshroom", MushroomCow.class, 96), + //SNOWMAN("snow_golem", Snowman.class, 97), + //OCELOT("ocelot", Ocelot.class, 98), + //IRON_GOLEM("iron_golem", IronGolem.class, 99), + //HORSE("horse", Horse.class, 100), + //RABBIT("rabbit", Rabbit.class, 101), + //POLAR_BEAR("polar_bear", PolarBear.class, 102), + //LLAMA("llama", Llama.class, 103), + //LLAMA_SPIT("llama_spit", LlamaSpit.class, 104), + //PARROT("parrot", Parrot.class, 105), + //VILLAGER("villager", Villager.class, 120), + //ENDER_CRYSTAL("end_crystal", EnderCrystal.class, 200), + //TURTLE("turtle", Turtle.class, -1), + //PHANTOM("phantom", Phantom.class, -1), + //TRIDENT("trident", Trident.class, -1), + //COD("cod", Cod.class, -1), + //SALMON("salmon", Salmon.class, -1), + //PUFFERFISH("pufferfish", PufferFish.class, -1), + //TROPICAL_FISH("tropical_fish", TropicalFish.class, -1), + //DROWNED("drowned", Drowned.class, -1), + //DOLPHIN("dolphin", Dolphin.class, -1), + //CAT("cat", Cat.class, -1), + //PANDA("panda", Panda.class, -1), + //PILLAGER("pillager", Pillager.class, -1), + //RAVAGER("ravager", Ravager.class, -1), + //TRADER_LLAMA("trader_llama", TraderLlama.class, -1), + //WANDERING_TRADER("wandering_trader", WanderingTrader.class, -1), + //FOX("fox", Fox.class, -1), + //BEE("bee", Bee.class, -1), + //HOGLIN("hoglin", Hoglin.class, -1), + //PIGLIN("piglin", Piglin.class, -1), + //STRIDER("strider", Strider.class, -1), + //ZOGLIN("zoglin", Zoglin.class, -1), + //PIGLIN_BRUTE("piglin_brute", PiglinBrute.class, -1), + /** + * A fishing line and bobber. + */ + //FISHING_HOOK("fishing_bobber", FishHook.class, -1, false), + /** + * A bolt of lightning. + *

+ * Spawn with {@link World#strikeLightning(Location)}. + */ + //LIGHTNING("lightning_bolt", LightningStrike.class, -1, false), + PLAYER("player", Player.class, 106, false), + /** + * An unknown entity without an Entity Class + */ + UNKNOWN(null, null, -1, false); + + private final String name; + private final Class clazz; + private final short typeId; + private final boolean independent; + private final boolean living; + private final NamespacedKey key; + + private static final Map NAME_MAP = new HashMap<>(); + private static final Map ID_MAP = new HashMap<>(); + + static { + for (EntityType type : values()) { + if (type.name != null) { + NAME_MAP.put(type.name.toLowerCase(java.util.Locale.ENGLISH), type); + } + if (type.typeId > 0) { + ID_MAP.put(type.typeId, type); + } + } + + // Add legacy names + /* + NAME_MAP.put("xp_orb", EXPERIENCE_ORB); + NAME_MAP.put("eye_of_ender_signal", ENDER_SIGNAL); + NAME_MAP.put("xp_bottle", THROWN_EXP_BOTTLE); + NAME_MAP.put("fireworks_rocket", FIREWORK); + NAME_MAP.put("evocation_fangs", EVOKER_FANGS); + NAME_MAP.put("evocation_illager", EVOKER); + NAME_MAP.put("vindication_illager", VINDICATOR); + NAME_MAP.put("illusion_illager", ILLUSIONER); + NAME_MAP.put("commandblock_minecart", MINECART_COMMAND); + NAME_MAP.put("snowman", SNOWMAN); + NAME_MAP.put("villager_golem", IRON_GOLEM); + NAME_MAP.put("ender_crystal", ENDER_CRYSTAL); + NAME_MAP.put("zombie_pigman", ZOMBIFIED_PIGLIN); + */ + } + + private EntityType(String name, Class clazz, int typeId) { + this(name, clazz, typeId, true); + } + + private EntityType(String name, Class clazz, int typeId, boolean independent) { + this.name = name; + this.clazz = clazz; + this.typeId = (short) typeId; + this.independent = independent; + this.living = clazz != null && LivingEntity.class.isAssignableFrom(clazz); + this.key = (name == null) ? null : NamespacedKey.minecraft(name); + } + + /** + * Gets the entity type name. + * + * @return the entity type's name + * @deprecated Magic value + */ + @Deprecated + public String getName() { + return name; + } + + public NamespacedKey getKey() { + return key; + } + + public Class getEntityClass() { + return clazz; + } + + /** + * Gets the entity network type id. + * + * @return the network type id + */ + public short getTypeId() { + return typeId; + } + + /** + * Gets an entity type from its name. + * + * @param name the entity type's name + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + public static EntityType fromName(String name) { + if (name == null) { + return null; + } + return NAME_MAP.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * Gets an entity from its id. + * + * @param id the raw type id + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + public static EntityType fromId(int id) { + if (id > Short.MAX_VALUE) { + return null; + } + return ID_MAP.get((short) id); + } + + /** + * Some entities cannot be spawned using {@link + * World#spawnEntity(Location, EntityType)} or {@link + * World#spawn(Location, Class)}, usually because they require additional + * information in order to spawn. + * + * @return False if the entity type cannot be spawned + */ + public boolean isSpawnable() { + return independent; + } + + public boolean isAlive() { + return living; + } +} diff --git a/src/main/java/com/loohp/limbo/Entity/LivingEntity.java b/src/main/java/com/loohp/limbo/Entity/LivingEntity.java new file mode 100644 index 0000000..223e1bf --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/LivingEntity.java @@ -0,0 +1,132 @@ +package com.loohp.limbo.Entity; + +import java.util.UUID; + +import com.loohp.limbo.Limbo; +import com.loohp.limbo.Entity.DataWatcher.WatchableField; +import com.loohp.limbo.Entity.DataWatcher.WatchableObjectType; +import com.loohp.limbo.Inventory.EquipmentSlot; +import com.loohp.limbo.Location.Location; +import com.loohp.limbo.World.BlockPosition; +import com.loohp.limbo.World.World; + +public abstract class LivingEntity extends Entity { + + @WatchableField(MetadataIndex = 7, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x01) + protected boolean handActive = false; + @WatchableField(MetadataIndex = 7, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x02) + protected boolean activeHand = false; //false = main hand, true = off hand + @WatchableField(MetadataIndex = 7, WatchableObjectType = WatchableObjectType.BYTE, IsBitmask = true, Bitmask = 0x04) + protected boolean inRiptideSpinAttack = false; + @WatchableField(MetadataIndex = 8, WatchableObjectType = WatchableObjectType.FLOAT) + protected float health = 1.0F; + @WatchableField(MetadataIndex = 9, WatchableObjectType = WatchableObjectType.VARINT) + protected int potionEffectColor = 0; + @WatchableField(MetadataIndex = 10, WatchableObjectType = WatchableObjectType.BOOLEAN) + protected boolean potionEffectAmbient = false; + @WatchableField(MetadataIndex = 11, WatchableObjectType = WatchableObjectType.VARINT) + protected int arrowsInEntity = 0; + @WatchableField(MetadataIndex = 12, WatchableObjectType = WatchableObjectType.VARINT) + protected int absorption = 0; + @WatchableField(MetadataIndex = 13, WatchableObjectType = WatchableObjectType.POSITION, IsOptional = true) + protected BlockPosition sleepingLocation = null; + + public LivingEntity(EntityType type, int entityId, UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + super(type, entityId, uuid, world, x, y, z, yaw, pitch); + } + + public LivingEntity(EntityType type, UUID uuid, World world, double x, double y, double z, float yaw, float pitch) { + this(type, Limbo.getInstance().getNextEntityId(), uuid, world, x, y, z, yaw, pitch); + } + + public LivingEntity(EntityType type, World world, double x, double y, double z, float yaw, float pitch) { + this(type, Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), world, x, y, z, yaw, pitch); + } + + public LivingEntity(EntityType type, UUID uuid, Location location) { + this(type, Limbo.getInstance().getNextEntityId(), uuid, location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public LivingEntity(EntityType type, Location location) { + this(type, Limbo.getInstance().getNextEntityId(), UUID.randomUUID(), location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + } + + public boolean isHandActive() { + return handActive; + } + + public void setHandActive(boolean handActive) { + this.handActive = handActive; + } + + public EquipmentSlot getActiveHand() { + return activeHand ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND; + } + + public void setActiveHand(EquipmentSlot activeHand) { + if (activeHand.equals(EquipmentSlot.MAINHAND)) { + this.activeHand = false; + } else if (activeHand.equals(EquipmentSlot.OFFHAND)) { + this.activeHand = true; + } else { + throw new IllegalArgumentException("Invalid EquipmentSlot " + activeHand.toString()); + } + } + + public boolean isInRiptideSpinAttack() { + return inRiptideSpinAttack; + } + + public void setInRiptideSpinAttack(boolean inRiptideSpinAttack) { + this.inRiptideSpinAttack = inRiptideSpinAttack; + } + + public float getHealth() { + return health; + } + + public void setHealth(float health) { + this.health = health; + } + + public int getPotionEffectColor() { + return potionEffectColor; + } + + public void setPotionEffectColor(int potionEffectColor) { + this.potionEffectColor = potionEffectColor; + } + + public boolean isPotionEffectAmbient() { + return potionEffectAmbient; + } + + public void setPotionEffectAmbient(boolean potionEffectAmbient) { + this.potionEffectAmbient = potionEffectAmbient; + } + + public int getArrowsInEntity() { + return arrowsInEntity; + } + + public void setArrowsInEntity(int arrowsInEntity) { + this.arrowsInEntity = arrowsInEntity; + } + + public int getAbsorption() { + return absorption; + } + + public void setAbsorption(int absorption) { + this.absorption = absorption; + } + + public BlockPosition getSleepingLocation() { + return sleepingLocation; + } + + public void setSleepingLocation(BlockPosition sleepingLocation) { + this.sleepingLocation = sleepingLocation; + } + +} diff --git a/src/main/java/com/loohp/limbo/Entity/Pose.java b/src/main/java/com/loohp/limbo/Entity/Pose.java new file mode 100644 index 0000000..ca125b8 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Entity/Pose.java @@ -0,0 +1,34 @@ +package com.loohp.limbo.Entity; + +public enum Pose { + + STANDING(0), + FALL_FLYING(1), + SLEEPING(2), + SWIMMING(3), + SPIN_ATTACK(4), + SNEAKING(5), + DYING(6); + + private static final Pose[] VALUES = values(); + + private int id; + + Pose(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static Pose fromId(int id) { + for (Pose pose : VALUES) { + if (id == pose.id) { + return pose; + } + } + return null; + } + +} diff --git a/src/main/java/com/loohp/limbo/File/FileConfiguration.java b/src/main/java/com/loohp/limbo/File/FileConfiguration.java index b27435d..56a13b4 100644 --- a/src/main/java/com/loohp/limbo/File/FileConfiguration.java +++ b/src/main/java/com/loohp/limbo/File/FileConfiguration.java @@ -3,9 +3,12 @@ package com.loohp.limbo.File; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; +import java.io.Reader; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; @@ -18,32 +21,36 @@ import com.loohp.limbo.Utils.YamlOrder; public class FileConfiguration { - File file; - - Map mapping; - String header; + private Map mapping; + private String header; public FileConfiguration(File file) throws FileNotFoundException { - this.file = file; if (file.exists()) { - reloadConfig(); + reloadConfig(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); } else { mapping = new LinkedHashMap<>(); } } - @Deprecated public FileConfiguration(InputStream input){ - reloadConfig(input); + reloadConfig(new InputStreamReader(input, StandardCharsets.UTF_8)); } - public FileConfiguration reloadConfig() throws FileNotFoundException { - return reloadConfig(new FileInputStream(file)); + public FileConfiguration(Reader reader){ + reloadConfig(reader); } - private FileConfiguration reloadConfig(InputStream input) { + public FileConfiguration reloadConfig(File file) throws FileNotFoundException { + return reloadConfig(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + } + + public FileConfiguration reloadConfig(InputStream input) { + return reloadConfig(new InputStreamReader(input, StandardCharsets.UTF_8)); + } + + public FileConfiguration reloadConfig(Reader reader) { Yaml yml = new Yaml(); - mapping = yml.load(input); + mapping = yml.load(reader); return this; } @@ -87,7 +94,29 @@ public class FileConfiguration { } } - public void saveConfig(File file) throws FileNotFoundException, UnsupportedEncodingException { + public String saveToString() throws IOException { + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setPrettyFlow(true); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Representer customRepresenter = new Representer(); + YamlOrder customProperty = new YamlOrder(); + customRepresenter.setPropertyUtils(customProperty); + Yaml yaml = new Yaml(customRepresenter, options); + + StringWriter writer = new StringWriter(); + PrintWriter pw = new PrintWriter(writer); + if (header != null) { + pw.println("#" + header.replace("\n", "\n#")); + } + yaml.dump(mapping, pw); + pw.flush(); + pw.close(); + + return writer.toString(); + } + + public void saveConfig(File file) throws IOException { DumperOptions options = new DumperOptions(); options.setIndent(2); options.setPrettyFlow(true); diff --git a/src/main/java/com/loohp/limbo/File/ServerProperties.java b/src/main/java/com/loohp/limbo/File/ServerProperties.java index dc80abf..4f3fee8 100644 --- a/src/main/java/com/loohp/limbo/File/ServerProperties.java +++ b/src/main/java/com/loohp/limbo/File/ServerProperties.java @@ -4,6 +4,10 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; @@ -16,29 +20,44 @@ import com.loohp.limbo.Utils.NamespacedKey; import com.loohp.limbo.World.World; public class ServerProperties { + + public static final String COMMENT = "For explaination of what each of the options does, please visit:\nhttps://github.com/LOOHP/Limbo/blob/master/src/main/resources/server.properties"; - File file; - int maxPlayers; - int serverPort; - String serverIp; - NamespacedKey levelName; - String schemFileName; - NamespacedKey levelDimension; - GameMode defaultGamemode; - Location worldSpawn; - boolean reducedDebugInfo; - boolean allowFlight; - String motdJson; - String versionString; - int protocol; - boolean bungeecord; + private File file; + private int maxPlayers; + private int serverPort; + private String serverIp; + private NamespacedKey levelName; + private String schemFileName; + private NamespacedKey levelDimension; + private GameMode defaultGamemode; + private Location worldSpawn; + private boolean reducedDebugInfo; + private boolean allowFlight; + private String motdJson; + private String versionString; + private int protocol; + private boolean bungeecord; + private int viewDistance; + private double ticksPerSecond; Optional favicon; public ServerProperties(File file) throws IOException { this.file = file; + + Properties def = new Properties(); + def.load(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("server.properties"), StandardCharsets.UTF_8)); + Properties prop = new Properties(); - prop.load(new FileInputStream(file)); + prop.load(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + + for (Entry entry : def.entrySet()) { + String key = entry.getKey().toString(); + String value = entry.getValue().toString(); + prop.putIfAbsent(key, value); + } + prop.store(new PrintWriter(file, StandardCharsets.UTF_8), COMMENT); protocol = Limbo.getInstance().serverImplmentationProtocol; @@ -63,6 +82,8 @@ public class ServerProperties { motdJson = prop.getProperty("motd"); versionString = prop.getProperty("version"); bungeecord = Boolean.parseBoolean(prop.getProperty("bungeecord")); + viewDistance = Integer.parseInt(prop.getProperty("view-distance")); + ticksPerSecond = Double.parseDouble(prop.getProperty("ticks-per-second")); File png = new File("server-icon.png"); if (png.exists()) { @@ -155,5 +176,13 @@ public class ServerProperties { public int getProtocol() { return protocol; } + + public int getViewDistance() { + return viewDistance; + } + + public double getDefinedTicksPerSecond() { + return ticksPerSecond; + } } diff --git a/src/main/java/com/loohp/limbo/Inventory/EquipmentSlot.java b/src/main/java/com/loohp/limbo/Inventory/EquipmentSlot.java new file mode 100644 index 0000000..fb7ddbe --- /dev/null +++ b/src/main/java/com/loohp/limbo/Inventory/EquipmentSlot.java @@ -0,0 +1,12 @@ +package com.loohp.limbo.Inventory; + +public enum EquipmentSlot { + + MAINHAND, + OFFHAND, + HELMENT, + CHESTPLATE, + LEGGINGS, + BOOTS; + +} diff --git a/src/main/java/com/loohp/limbo/Limbo.java b/src/main/java/com/loohp/limbo/Limbo.java index 54d8988..3db0f03 100644 --- a/src/main/java/com/loohp/limbo/Limbo.java +++ b/src/main/java/com/loohp/limbo/Limbo.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -108,6 +109,8 @@ public class Limbo { public final int serverImplmentationProtocol = 754; public final String limboImplementationVersion; + private AtomicBoolean isRunning; + private ServerConnection server; private Console console; @@ -126,6 +129,9 @@ public class Limbo { private DimensionRegistry dimensionRegistry; + @SuppressWarnings("unused") + private Tick tick; + private Metrics metrics; public AtomicInteger entityIdCount = new AtomicInteger(); @@ -136,6 +142,7 @@ public class Limbo { @SuppressWarnings("unchecked") public Limbo() throws IOException, ParseException, NumberFormatException, ClassNotFoundException, InterruptedException { instance = this; + isRunning = new AtomicBoolean(true); if (!noGui) { while (!GUI.loadFinish) { @@ -304,6 +311,8 @@ public class Limbo { server = new ServerConnection(properties.getServerIp(), properties.getServerPort()); + tick = new Tick(this); + metrics = new Metrics(); console.run(); @@ -357,6 +366,27 @@ public class Limbo { return world; } + public void registerWorld(World world) { + if (!worlds.contains(world)) { + worlds.add(world); + } else { + throw new RuntimeException("World already registered"); + } + } + + public void unregisterWorld(World world) { + if (worlds.indexOf(world) == 0) { + throw new RuntimeException("World already registered"); + } else if (!worlds.contains(world)) { + throw new RuntimeException("World not registered"); + } else { + for (Player player : world.getPlayers()) { + player.teleport(properties.getWorldSpawn()); + } + worlds.remove(world); + } + } + public ServerProperties getServerProperties() { return properties; } @@ -387,12 +417,12 @@ public class Limbo { public void addPlayer(Player player) { playersByName.put(player.getName(), player); - playersByUUID.put(player.getUUID(), player); + playersByUUID.put(player.getUniqueId(), player); } public void removePlayer(Player player) { playersByName.remove(player.getName()); - playersByUUID.remove(player.getUUID()); + playersByUUID.remove(player.getUniqueId()); } public List getWorlds() { @@ -453,6 +483,7 @@ public class Limbo { } public void stopServer() { + isRunning.set(false); console.sendMessage("Stopping Server..."); for (LimboPlugin plugin : Limbo.getInstance().getPluginManager().getPlugins()) { @@ -476,6 +507,10 @@ public class Limbo { System.exit(0); } + public boolean isRunning() { + return isRunning.get(); + } + public int getNextEntityId() { if (entityIdCount.get() == Integer.MAX_VALUE) { return entityIdCount.getAndSet(0); diff --git a/src/main/java/com/loohp/limbo/Player/Player.java b/src/main/java/com/loohp/limbo/Player/Player.java index 7b18ee9..2fd109e 100644 --- a/src/main/java/com/loohp/limbo/Player/Player.java +++ b/src/main/java/com/loohp/limbo/Player/Player.java @@ -5,6 +5,11 @@ import java.util.UUID; import com.loohp.limbo.Limbo; import com.loohp.limbo.Commands.CommandSender; +import com.loohp.limbo.Entity.DataWatcher; +import com.loohp.limbo.Entity.DataWatcher.WatchableField; +import com.loohp.limbo.Entity.DataWatcher.WatchableObjectType; +import com.loohp.limbo.Entity.EntityType; +import com.loohp.limbo.Entity.LivingEntity; import com.loohp.limbo.Events.PlayerChatEvent; import com.loohp.limbo.Events.PlayerTeleportEvent; import com.loohp.limbo.Location.Location; @@ -14,30 +19,42 @@ import com.loohp.limbo.Server.Packets.PacketPlayOutGameState; import com.loohp.limbo.Server.Packets.PacketPlayOutPositionAndLook; import com.loohp.limbo.Server.Packets.PacketPlayOutRespawn; import com.loohp.limbo.Utils.GameMode; -import com.loohp.limbo.World.World; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.chat.ComponentSerializer; -public class Player implements CommandSender { +public class Player extends LivingEntity implements CommandSender { public final ClientConnection clientConnection; + public final PlayerInteractManager playerInteractManager; - private final String username; - private final UUID uuid; + protected final String username; protected GameMode gamemode; + protected DataWatcher watcher; - protected int entityId; - - private Location location; + @WatchableField(MetadataIndex = 14, WatchableObjectType = WatchableObjectType.FLOAT) + protected float additionalHearts = 0.0F; + @WatchableField(MetadataIndex = 15, WatchableObjectType = WatchableObjectType.VARINT) + protected int score = 0; + @WatchableField(MetadataIndex = 16, WatchableObjectType = WatchableObjectType.BYTE) + protected byte skinLayers = 0; + @WatchableField(MetadataIndex = 17, WatchableObjectType = WatchableObjectType.BYTE) + protected byte mainHand = 1; + //@WatchableField(MetadataIndex = 18, WatchableObjectType = WatchableObjectType.NBT) + //protected Entity leftShoulder = null; + //@WatchableField(MetadataIndex = 19, WatchableObjectType = WatchableObjectType.NBT) + //protected Entity rightShoulder = null; - public Player(ClientConnection clientConnection, String username, UUID uuid, int entityId, Location location) { + public Player(ClientConnection clientConnection, String username, UUID uuid, int entityId, Location location, PlayerInteractManager playerInteractManager) throws IllegalArgumentException, IllegalAccessException { + super(EntityType.PLAYER, entityId, uuid, location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); this.clientConnection = clientConnection; this.username = username; - this.uuid = uuid; this.entityId = entityId; - this.location = location.clone(); + this.playerInteractManager = playerInteractManager; + this.playerInteractManager.setPlayer(this); + this.watcher = new DataWatcher(this); + this.watcher.update(); } public GameMode getGamemode() { @@ -55,41 +72,95 @@ public class Player implements CommandSender { } this.gamemode = gamemode; } + + @Deprecated + protected void setEntityId(int entityId) { + this.entityId = entityId; + } - public World getWorld() { - return location.clone().getWorld(); + public float getAdditionalHearts() { + return additionalHearts; + } + + public void setAdditionalHearts(float additionalHearts) { + this.additionalHearts = additionalHearts; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + public byte getSkinLayers() { + return skinLayers; + } + + public void setSkinLayers(byte skinLayers) { + this.skinLayers = skinLayers; + } + + public byte getMainHand() { + return mainHand; + } + + public void setMainHand(byte mainHand) { + this.mainHand = mainHand; } - public int getEntityId() { - return entityId; + @Override + public DataWatcher getDataWatcher() { + return watcher; + } + + @Override + public boolean isValid() { + return Limbo.getInstance().getPlayers().contains(this); + } + + @Override + public void remove() { + + } + + /* + public Entity getLeftShoulder() { + return leftShoulder; } - public Location getLocation() { - return location.clone(); + public void setLeftShoulder(Entity leftShoulder) { + this.leftShoulder = leftShoulder; } - public void setLocation(Location location) { - this.location = location; + public Entity getRightShoulder() { + return rightShoulder; } + public void setRightShoulder(Entity rightShoulder) { + this.rightShoulder = rightShoulder; + } + */ + + @Override public String getName() { return username; } - - public UUID getUUID() { - return uuid; - } + @Override public boolean hasPermission(String permission) { return Limbo.getInstance().getPermissionsManager().hasPermission(this, permission); } + @Override public void teleport(Location location) { PlayerTeleportEvent event = Limbo.getInstance().getEventsManager().callEvent(new PlayerTeleportEvent(this, getLocation(), location)); if (!event.isCancelled()) { location = event.getTo(); + super.teleport(location); try { - if (!this.location.getWorld().equals(location.getWorld())) { + if (!world.equals(location.getWorld())) { PacketPlayOutRespawn respawn = new PacketPlayOutRespawn(location.getWorld(), Limbo.getInstance().getDimensionRegistry().getCodec(), 0, gamemode, false, false, true); clientConnection.sendPacket(respawn); } @@ -101,6 +172,10 @@ public class Player implements CommandSender { } } + protected void setLocation(Location location) { + super.teleport(location); + } + public void sendMessage(String message, UUID uuid) { sendMessage(TextComponent.fromLegacyText(message), uuid); } diff --git a/src/main/java/com/loohp/limbo/Player/PlayerInteractManager.java b/src/main/java/com/loohp/limbo/Player/PlayerInteractManager.java new file mode 100644 index 0000000..d132fdf --- /dev/null +++ b/src/main/java/com/loohp/limbo/Player/PlayerInteractManager.java @@ -0,0 +1,135 @@ +package com.loohp.limbo.Player; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import com.loohp.limbo.Limbo; +import com.loohp.limbo.Entity.Entity; +import com.loohp.limbo.Location.Location; +import com.loohp.limbo.Server.Packets.PacketPlayOutEntityDestroy; +import com.loohp.limbo.Server.Packets.PacketPlayOutEntityMetadata; +import com.loohp.limbo.Server.Packets.PacketPlayOutLightUpdate; +import com.loohp.limbo.Server.Packets.PacketPlayOutMapChunk; +import com.loohp.limbo.Server.Packets.PacketPlayOutSpawnEntity; +import com.loohp.limbo.Server.Packets.PacketPlayOutSpawnEntityLiving; +import com.loohp.limbo.Server.Packets.PacketPlayOutUnloadChunk; +import com.loohp.limbo.World.World; + +import net.querz.mca.Chunk; + +public class PlayerInteractManager { + + private Player player; + + private Set entities; + private Map chunks; + + public PlayerInteractManager() { + this.player = null; + this.entities = new HashSet<>(); + this.chunks = new HashMap<>(); + } + + protected void setPlayer(Player player) { + if (this.player == null) { + this.player = player; + } else { + throw new RuntimeException("Player in PlayerInteractManager cannot be changed once created"); + } + } + + public Player getPlayer() { + return player; + } + + public void update() throws IOException { + int viewDistanceChunks = Limbo.getInstance().getServerProperties().getViewDistance(); + int viewDistanceBlocks = viewDistanceChunks << 4; + Location location = player.getLocation(); + Set entitiesInRange = player.getWorld().getEntities().stream().filter(each -> each.getLocation().distanceSquared(location) < viewDistanceBlocks * viewDistanceBlocks).collect(Collectors.toSet()); + for (Entity entity : entitiesInRange) { + if (!entities.contains(entity)) { + if (entity.getType().isAlive()) { + PacketPlayOutSpawnEntityLiving packet = new PacketPlayOutSpawnEntityLiving(entity.getEntityId(), entity.getUniqueId(), entity.getType(), entity.getX(), entity.getY(), entity.getZ(), entity.getYaw(), entity.getPitch(), entity.getPitch(), (short) 0, (short) 0, (short) 0); + player.clientConnection.sendPacket(packet); + + PacketPlayOutEntityMetadata meta = new PacketPlayOutEntityMetadata(entity); + player.clientConnection.sendPacket(meta); + } else { + PacketPlayOutSpawnEntity packet = new PacketPlayOutSpawnEntity(entity.getEntityId(), entity.getUniqueId(), entity.getType(), entity.getX(), entity.getY(), entity.getZ(), entity.getPitch(), entity.getYaw(), (short) 0, (short) 0, (short) 0); + player.clientConnection.sendPacket(packet); + + PacketPlayOutEntityMetadata meta = new PacketPlayOutEntityMetadata(entity); + player.clientConnection.sendPacket(meta); + } + } + } + List ids = new ArrayList<>(); + for (Entity entity : entities) { + if (!entitiesInRange.contains(entity)) { + ids.add(entity.getEntityId()); + } + } + PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(ids.stream().mapToInt(each -> each).toArray()); + player.clientConnection.sendPacket(packet); + + entities = entitiesInRange; + + int playerChunkX = (int) location.getX() >> 4; + int playerChunkZ = (int) location.getZ() >> 4; + World world = location.getWorld(); + + Set chunksInRange = new HashSet<>(); + + for (int x = playerChunkX - viewDistanceChunks; x < playerChunkX + viewDistanceChunks; x++) { + for (int z = playerChunkZ - viewDistanceChunks; z < playerChunkZ + viewDistanceChunks; z++) { + Chunk chunk = world.getChunkAt(x, z); + if (chunk != null) { + chunksInRange.add(chunk); + } + } + } + + for (Entry entry : chunks.entrySet()) { + Chunk chunk = entry.getKey(); + if (location.getWorld().getChunkXZ(chunk) == null) { + World world0 = entry.getValue(); + int[] chunkPos = world0.getChunkXZ(chunk); + PacketPlayOutUnloadChunk packet0 = new PacketPlayOutUnloadChunk(chunkPos[0], chunkPos[1]); + player.clientConnection.sendPacket(packet0); + } + } + + for (Chunk chunk : chunksInRange) { + if (!chunks.containsKey(chunk)) { + int[] chunkPos = world.getChunkXZ(chunk); + PacketPlayOutMapChunk packet0 = new PacketPlayOutMapChunk(chunkPos[0], chunkPos[1], chunk, world.getEnvironment()); + player.clientConnection.sendPacket(packet0); + + List blockChunk = world.getLightEngineBlock().getBlockLightBitMask(chunkPos[0], chunkPos[1]); + if (blockChunk == null) { + blockChunk = new ArrayList<>(); + } + List skyChunk = null; + if (world.hasSkyLight()) { + skyChunk = world.getLightEngineSky().getSkyLightBitMask(chunkPos[0], chunkPos[1]); + } + if (skyChunk == null) { + skyChunk = new ArrayList<>(); + } + PacketPlayOutLightUpdate chunkdata = new PacketPlayOutLightUpdate(chunkPos[0], chunkPos[1], true, skyChunk, blockChunk); + player.clientConnection.sendPacket(chunkdata); + } + } + + chunks = chunksInRange.stream().collect(Collectors.toMap(each -> each, each -> world)); + } + +} diff --git a/src/main/java/com/loohp/limbo/Player/Unsafe.java b/src/main/java/com/loohp/limbo/Player/Unsafe.java index fe1c64f..b6864eb 100644 --- a/src/main/java/com/loohp/limbo/Player/Unsafe.java +++ b/src/main/java/com/loohp/limbo/Player/Unsafe.java @@ -1,5 +1,6 @@ package com.loohp.limbo.Player; +import com.loohp.limbo.Location.Location; import com.loohp.limbo.Utils.GameMode; @Deprecated @@ -14,7 +15,12 @@ public class Unsafe { @Deprecated public void a(Player a, int b) { - a.entityId = b; + a.setEntityId(b); + } + + @Deprecated + public void a(Player a, Location b) { + a.setLocation(b); } } diff --git a/src/main/java/com/loohp/limbo/Plugins/PluginManager.java b/src/main/java/com/loohp/limbo/Plugins/PluginManager.java index f6da174..8fd75ea 100644 --- a/src/main/java/com/loohp/limbo/Plugins/PluginManager.java +++ b/src/main/java/com/loohp/limbo/Plugins/PluginManager.java @@ -44,7 +44,6 @@ public class PluginManager { if (name.endsWith("plugin.yml") || name.endsWith("limbo.yml")) { found = true; - @SuppressWarnings("deprecation") FileConfiguration pluginYaml = new FileConfiguration(zip); String main = pluginYaml.get("main", String.class); String pluginName = pluginYaml.get("name", String.class); diff --git a/src/main/java/com/loohp/limbo/Server/ClientConnection.java b/src/main/java/com/loohp/limbo/Server/ClientConnection.java index 4505828..5a57259 100644 --- a/src/main/java/com/loohp/limbo/Server/ClientConnection.java +++ b/src/main/java/com/loohp/limbo/Server/ClientConnection.java @@ -26,6 +26,7 @@ import com.loohp.limbo.Events.StatusPingEvent; import com.loohp.limbo.File.ServerProperties; import com.loohp.limbo.Location.Location; import com.loohp.limbo.Player.Player; +import com.loohp.limbo.Player.PlayerInteractManager; import com.loohp.limbo.Server.Packets.Packet; import com.loohp.limbo.Server.Packets.PacketHandshakingIn; import com.loohp.limbo.Server.Packets.PacketLoginInLoginStart; @@ -40,9 +41,8 @@ import com.loohp.limbo.Server.Packets.PacketPlayInRotation; import com.loohp.limbo.Server.Packets.PacketPlayInTabComplete; import com.loohp.limbo.Server.Packets.PacketPlayOutDeclareCommands; import com.loohp.limbo.Server.Packets.PacketPlayOutDisconnect; -import com.loohp.limbo.Server.Packets.PacketPlayOutLightUpdate; +import com.loohp.limbo.Server.Packets.PacketPlayOutEntityMetadata; import com.loohp.limbo.Server.Packets.PacketPlayOutLogin; -import com.loohp.limbo.Server.Packets.PacketPlayOutMapChunk; import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerAbilities; import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags; import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo; @@ -50,7 +50,6 @@ import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoAction; import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoData; import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty; import com.loohp.limbo.Server.Packets.PacketPlayOutPositionAndLook; -import com.loohp.limbo.Server.Packets.PacketPlayOutShowPlayerSkins; import com.loohp.limbo.Server.Packets.PacketPlayOutSpawnPosition; import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete; import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete.TabCompleteMatches; @@ -72,11 +71,10 @@ import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.chat.ComponentSerializer; -import net.querz.mca.Chunk; public class ClientConnection extends Thread { - public enum ClientState { + public static enum ClientState { LEGACY, HANDSHAKE, STATUS, @@ -97,10 +95,14 @@ public class ClientConnection extends Thread { private InetAddress inetAddress; + private boolean ready; + public ClientConnection(Socket client_socket) { this.client_socket = client_socket; this.inetAddress = client_socket.getInetAddress(); this.lastKeepAlivePayLoad = new AtomicLong(); + this.running = false; + this.ready = false; } public InetAddress getInetAddress() { @@ -131,6 +133,10 @@ public class ClientConnection extends Thread { return running; } + public boolean isReady() { + return ready; + } + public synchronized void sendPacket(PacketOut packet) throws IOException { byte[] packetByte = packet.serializePacket(); DataTypeIO.writeVarInt(output, packetByte.length); @@ -232,11 +238,13 @@ public class ClientConnection extends Thread { bungeeUUID = UUID.fromString(data[2].replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5")); inetAddress = InetAddress.getByName(ip); - String skinJson = data[3]; - - String skin = skinJson.split("\"value\":\"")[1].split("\"")[0]; - String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0]; - bungeeSkin = new SkinResponse(skin, signature); + if (data.length > 3) { + String skinJson = data[3]; + + String skin = skinJson.split("\"value\":\"")[1].split("\"")[0]; + String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0]; + bungeeSkin = new SkinResponse(skin, signature); + } } catch (Exception e) { Limbo.getInstance().getConsole().sendMessage("If you wish to use bungeecord's IP forwarding, please enable that in your bungeecord config.yml as well!"); disconnectDuringLogin(new BaseComponent[] {new TextComponent(ChatColor.RED + "Please connect from the proxy!")}); @@ -260,7 +268,8 @@ public class ClientConnection extends Thread { state = ClientState.PLAY; - player = new Player(this, username, uuid, Limbo.getInstance().getNextEntityId(), Limbo.getInstance().getServerProperties().getWorldSpawn()); + player = new Player(this, username, uuid, Limbo.getInstance().getNextEntityId(), Limbo.getInstance().getServerProperties().getWorldSpawn(), new PlayerInteractManager()); + player.setSkinLayers((byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40)); Limbo.getInstance().addPlayer(player); break; @@ -292,37 +301,11 @@ public class ClientConnection extends Thread { World world = s.getWorld(); - for (int x = 0; x < world.getChunks().length; x++) { - for (int z = 0; z < world.getChunks()[x].length; z++) { - Chunk chunk = world.getChunks()[x][z]; - if (chunk != null) { - PacketPlayOutMapChunk chunkdata = new PacketPlayOutMapChunk(x, z, chunk, world.getEnvironment()); - sendPacket(chunkdata); - } - } - } - - for (int x = 0; x < world.getChunks().length; x++) { - for (int z = 0; z < world.getChunks()[x].length; z++) { - List blockChunk = world.getLightEngineBlock().getBlockLightBitMask(x, z); - if (blockChunk == null) { - blockChunk = new ArrayList<>(); - } - List skyChunk = null; - if (world.hasSkyLight()) { - skyChunk = world.getLightEngineSky().getSkyLightBitMask(x, z); - } - if (skyChunk == null) { - skyChunk = new ArrayList<>(); - } - PacketPlayOutLightUpdate chunkdata = new PacketPlayOutLightUpdate(x, z, true, skyChunk, blockChunk); - sendPacket(chunkdata); - } - } + player.playerInteractManager.update(); - SkinResponse skinresponce = isBungeecord ? bungeeSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName()); + SkinResponse skinresponce = isBungeecord && bungeeSkin != null ? bungeeSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName()); PlayerSkinProperty skin = skinresponce != null ? new PlayerSkinProperty(skinresponce.getSkin(), skinresponce.getSignature()) : null; - PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUUID(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), p.getDefaultGamemode(), 0, false, Optional.empty())); + PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUniqueId(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), p.getDefaultGamemode(), 0, false, Optional.empty())); sendPacket(info); /* for (ClientConnection client : Limbo.getInstance().getServerConnection().getClients()) { @@ -332,9 +315,6 @@ public class ClientConnection extends Thread { } */ - PacketPlayOutShowPlayerSkins show = new PacketPlayOutShowPlayerSkins(player.getEntityId()); - sendPacket(show); - Set flags = new HashSet<>(); if (p.isAllowFlight()) { flags.add(PlayerAbilityFlags.FLY); @@ -359,9 +339,15 @@ public class ClientConnection extends Thread { sendPacket(spawnPos); PacketPlayOutPositionAndLook positionLook = new PacketPlayOutPositionAndLook(s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch(), 1); - player.setLocation(new Location(world, s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch())); + Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, new Location(world, s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch())); sendPacket(positionLook); + player.getDataWatcher().update(); + PacketPlayOutEntityMetadata show = new PacketPlayOutEntityMetadata(player, false, Player.class.getDeclaredField("skinLayers")); + sendPacket(show); + + ready = true; + while (client_socket.isConnected()) { try { int size = DataTypeIO.readVarInt(input); @@ -381,7 +367,7 @@ public class ClientConnection extends Thread { PacketPlayOutPositionAndLook cancel = new PacketPlayOutPositionAndLook(returnTo.getX(), returnTo.getY(), returnTo.getZ(), returnTo.getYaw(), returnTo.getPitch(), 1); sendPacket(cancel); } else { - player.setLocation(event.getTo()); + Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, event.getTo()); PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4); sendPacket(response); } @@ -396,7 +382,7 @@ public class ClientConnection extends Thread { PacketPlayOutPositionAndLook cancel = new PacketPlayOutPositionAndLook(returnTo.getX(), returnTo.getY(), returnTo.getZ(), returnTo.getYaw(), returnTo.getPitch(), 1); sendPacket(cancel); } else { - player.setLocation(event.getTo()); + Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, event.getTo()); PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4); sendPacket(response); } @@ -411,7 +397,7 @@ public class ClientConnection extends Thread { PacketPlayOutPositionAndLook cancel = new PacketPlayOutPositionAndLook(returnTo.getX(), returnTo.getY(), returnTo.getZ(), returnTo.getYaw(), returnTo.getPitch(), 1); sendPacket(cancel); } else { - player.setLocation(event.getTo()); + Limbo.getInstance().getUnsafe().setPlayerLocationSilently(player, event.getTo()); PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4); sendPacket(response); } @@ -457,7 +443,7 @@ public class ClientConnection extends Thread { } - } catch (Exception e) {} + } catch (Exception e) {e.printStackTrace();} try { client_socket.close(); diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutShowPlayerSkins.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityDestroy.java similarity index 52% rename from src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutShowPlayerSkins.java rename to src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityDestroy.java index 8aa261d..5f99b40 100644 --- a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutShowPlayerSkins.java +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityDestroy.java @@ -6,16 +6,16 @@ import java.io.IOException; import com.loohp.limbo.Utils.DataTypeIO; -public class PacketPlayOutShowPlayerSkins extends PacketOut { - - private int entityId; - - public PacketPlayOutShowPlayerSkins(int entityId) { - this.entityId = entityId; - } +public class PacketPlayOutEntityDestroy extends PacketOut { - public int getEntityId() { - return entityId; + private int[] entityIds; + + public PacketPlayOutEntityDestroy(int... entityIds) { + this.entityIds = entityIds; + } + + public int[] getEntityIds() { + return entityIds; } @Override @@ -24,13 +24,10 @@ public class PacketPlayOutShowPlayerSkins extends PacketOut { DataOutputStream output = new DataOutputStream(buffer); output.writeByte(Packet.getPlayOut().get(getClass())); - - DataTypeIO.writeVarInt(output, entityId); - output.writeByte(16); - DataTypeIO.writeVarInt(output, 0); - int bitmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40; - output.writeByte(bitmask); - output.writeByte(0xff); + DataTypeIO.writeVarInt(output, entityIds.length); + for (int id : entityIds) { + DataTypeIO.writeVarInt(output, id); + } return buffer.toByteArray(); } diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityMetadata.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityMetadata.java new file mode 100644 index 0000000..e2caf8a --- /dev/null +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutEntityMetadata.java @@ -0,0 +1,156 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import com.loohp.limbo.Entity.DataWatcher.WatchableObject; +import com.loohp.limbo.Entity.DataWatcher.WatchableObjectType; +import com.loohp.limbo.Entity.Entity; +import com.loohp.limbo.Entity.Pose; +import com.loohp.limbo.Utils.DataTypeIO; +import com.loohp.limbo.Utils.Rotation3f; +import com.loohp.limbo.World.BlockPosition; + +import net.md_5.bungee.chat.ComponentSerializer; + +public class PacketPlayOutEntityMetadata extends PacketOut { + + public static final int END_OFF_METADATA = 0xff; + + private Entity entity; + public boolean allFields; + public Field[] fields; + + public PacketPlayOutEntityMetadata(Entity entity, boolean allFields, Field... fields) { + this.entity = entity; + this.allFields = allFields; + this.fields = fields; + } + + public PacketPlayOutEntityMetadata(Entity entity) { + this(entity, true); + } + + public Entity getEntity() { + return entity; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + DataTypeIO.writeVarInt(output, entity.getEntityId()); + Collection watches; + if (allFields) { + watches = new HashSet<>(entity.getDataWatcher().getWatchableObjects().values()); + } else { + watches = new HashSet<>(); + Map entries = entity.getDataWatcher().getWatchableObjects(); + for (Field field : fields) { + WatchableObject watch = entries.get(field); + if (watch != null) { + watches.add(watch); + } + } + } + + Map bitmasks = new HashMap<>(); + Iterator itr = watches.iterator(); + while (itr.hasNext()) { + WatchableObject watch = itr.next(); + if (watch.isBitmask()) { + itr.remove(); + Integer bitmask = bitmasks.get(watch.getIndex()); + if (bitmask == null) { + bitmask = 0; + } + if ((boolean) watch.getValue()) { + bitmask |= watch.getBitmask(); + } else { + bitmask &= ~watch.getBitmask(); + } + bitmasks.put(watch.getIndex(), bitmask); + } + } + for (Entry entry : bitmasks.entrySet()) { + watches.add(new WatchableObject(entry.getValue().byteValue(), entry.getKey(), WatchableObjectType.BYTE)); + } + + for (WatchableObject watch : watches) { + output.writeByte(watch.getIndex()); + if (watch.isOptional()) { + DataTypeIO.writeVarInt(output, watch.getType().getOptionalTypeId()); + output.writeBoolean(watch.getValue() != null); + } else { + DataTypeIO.writeVarInt(output, watch.getType().getTypeId()); + } + if (!watch.isOptional() || watch.getValue() != null) { + switch (watch.getType()) { + //case BLOCKID: + // break; + case POSITION: + DataTypeIO.writeBlockPosition(output, (BlockPosition) watch.getValue()); + break; + case BOOLEAN: + output.writeBoolean((boolean) watch.getValue()); + break; + case BYTE: + output.writeByte((byte) watch.getValue()); + break; + case CHAT: + DataTypeIO.writeString(output, ComponentSerializer.toString(watch.getValue()), StandardCharsets.UTF_8); + break; + //case DIRECTION: + // break; + case FLOAT: + output.writeFloat((float) watch.getValue()); + break; + //case NBT: + // break; + //case PARTICLE: + // break; + case POSE: + DataTypeIO.writeVarInt(output, ((Pose) watch.getValue()).getId()); + break; + case ROTATION: + Rotation3f rotation = (Rotation3f) watch.getValue(); + output.writeFloat((float) rotation.getX()); + output.writeFloat((float) rotation.getY()); + output.writeFloat((float) rotation.getZ()); + break; + //case SLOT: + // break; + case STRING: + DataTypeIO.writeString(output, watch.getValue().toString(), StandardCharsets.UTF_8); + break; + case UUID: + DataTypeIO.writeUUID(output, (UUID) watch.getValue()); + break; + case VARINT: + DataTypeIO.writeVarInt(output, (int) watch.getValue()); + break; + //case VILLAGER_DATA: + // break; + default: + break; + } + } + } + output.writeByte(END_OFF_METADATA); + + return buffer.toByteArray(); + } + +} diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutRespawn.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutRespawn.java index ff275ee..4fe72da 100644 --- a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutRespawn.java +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutRespawn.java @@ -25,8 +25,7 @@ public class PacketPlayOutRespawn extends PacketOut { private boolean isFlat; private boolean copyMetaData; - public PacketPlayOutRespawn(World world, CompoundTag dimensionCodec, long hashedSeed, GameMode gamemode, boolean isDebug, - boolean isFlat, boolean copyMetaData) { + public PacketPlayOutRespawn(World world, CompoundTag dimensionCodec, long hashedSeed, GameMode gamemode, boolean isDebug, boolean isFlat, boolean copyMetaData) { this.dimension = world.getEnvironment(); this.dimensionCodec = dimensionCodec; this.worldName = new NamespacedKey(world.getName()).toString(); diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntity.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntity.java new file mode 100644 index 0000000..201e740 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntity.java @@ -0,0 +1,111 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +import com.loohp.limbo.Entity.EntityType; +import com.loohp.limbo.Utils.DataTypeIO; + +public class PacketPlayOutSpawnEntity extends PacketOut { + + private int entityId; + private UUID uuid; + private EntityType type; + private double x; + private double y; + private double z; + private float pitch; + private float yaw; + private int data; + private short velocityX; + private short velocityY; + private short velocityZ; + + public PacketPlayOutSpawnEntity(int entityId, UUID uuid, EntityType type, double x, double y, double z, float pitch, float yaw, short velocityX, short velocityY, short velocityZ) { + this.entityId = entityId; + this.uuid = uuid; + this.type = type; + this.x = x; + this.y = y; + this.z = z; + this.pitch = pitch; + this.yaw = yaw; + this.data = 0; //TO-DO + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + } + + public int getEntityId() { + return entityId; + } + + public UUID getUuid() { + return uuid; + } + + public EntityType getType() { + return type; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public float getPitch() { + return pitch; + } + + public float getYaw() { + return yaw; + } + + public int getData() { + return data; + } + + public short getVelocityX() { + return velocityX; + } + + public short getVelocityY() { + return velocityY; + } + + public short getVelocityZ() { + return velocityZ; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + DataTypeIO.writeVarInt(output, entityId); + DataTypeIO.writeUUID(output, uuid); + DataTypeIO.writeVarInt(output, type.getTypeId()); + output.writeDouble(x); + output.writeDouble(y); + output.writeDouble(z); + output.writeByte((byte) (int) (pitch * 256.0F / 360.0F)); + output.writeByte((byte) (int) (yaw * 256.0F / 360.0F)); + output.writeInt(data); + output.writeShort((int) (velocityX * 8000)); + output.writeShort((int) (velocityY * 8000)); + output.writeShort((int) (velocityZ * 8000)); + + return buffer.toByteArray(); + } + +} diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntityLiving.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntityLiving.java new file mode 100644 index 0000000..c14018d --- /dev/null +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutSpawnEntityLiving.java @@ -0,0 +1,111 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +import com.loohp.limbo.Entity.EntityType; +import com.loohp.limbo.Utils.DataTypeIO; + +public class PacketPlayOutSpawnEntityLiving extends PacketOut { + + private int entityId; + private UUID uuid; + private EntityType type; + private double x; + private double y; + private double z; + private float yaw; + private float pitch; + private float headPitch; + private short velocityX; + private short velocityY; + private short velocityZ; + + public PacketPlayOutSpawnEntityLiving(int entityId, UUID uuid, EntityType type, double x, double y, double z, float yaw, float pitch, float headPitch, short velocityX, short velocityY, short velocityZ) { + this.entityId = entityId; + this.uuid = uuid; + this.type = type; + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + this.headPitch = headPitch; + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + } + + public int getEntityId() { + return entityId; + } + + public UUID getUuid() { + return uuid; + } + + public EntityType getType() { + return type; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public float getYaw() { + return yaw; + } + + public float getPitch() { + return pitch; + } + + public float getHeadPitch() { + return headPitch; + } + + public short getVelocityX() { + return velocityX; + } + + public short getVelocityY() { + return velocityY; + } + + public short getVelocityZ() { + return velocityZ; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + DataTypeIO.writeVarInt(output, entityId); + DataTypeIO.writeUUID(output, uuid); + DataTypeIO.writeVarInt(output, type.getTypeId()); + output.writeDouble(x); + output.writeDouble(y); + output.writeDouble(z); + output.writeByte((byte) (int) (yaw * 256.0F / 360.0F)); + output.writeByte((byte) (int) (pitch * 256.0F / 360.0F)); + output.writeByte((byte) (int) (headPitch * 256.0F / 360.0F)); + output.writeShort((int) (velocityX * 8000)); + output.writeShort((int) (velocityY * 8000)); + output.writeShort((int) (velocityZ * 8000)); + + return buffer.toByteArray(); + } + +} diff --git a/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutUnloadChunk.java b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutUnloadChunk.java new file mode 100644 index 0000000..5fd7e1a --- /dev/null +++ b/src/main/java/com/loohp/limbo/Server/Packets/PacketPlayOutUnloadChunk.java @@ -0,0 +1,37 @@ +package com.loohp.limbo.Server.Packets; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class PacketPlayOutUnloadChunk extends PacketOut { + + private int chunkX; + private int chunkZ; + + public PacketPlayOutUnloadChunk(int chunkX, int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } + + @Override + public byte[] serializePacket() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(Packet.getPlayOut().get(getClass())); + output.writeInt(chunkX); + output.writeInt(chunkZ); + + return buffer.toByteArray(); + } + +} diff --git a/src/main/java/com/loohp/limbo/Tick.java b/src/main/java/com/loohp/limbo/Tick.java new file mode 100644 index 0000000..535c484 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Tick.java @@ -0,0 +1,51 @@ +package com.loohp.limbo; + +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +public class Tick { + + private TimerTask timerTask; + + public Tick(Limbo instance) { + this.timerTask = new TimerTask () { + @Override + public void run () { + if (instance.isRunning()) { + instance.getPlayers().forEach(each -> { + if (each.clientConnection.isReady()) { + try { + each.playerInteractManager.update(); + } catch (IOException e) { + e.printStackTrace(); + } + /* + try { + each.getDataWatcher().update(); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + */ + } + }); + instance.getWorlds().forEach(each -> { + try { + each.update(); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + }); + } else { + this.cancel(); + } + } + }; + new Timer().schedule(timerTask, 0, Math.round(1000 / instance.getServerProperties().getDefinedTicksPerSecond())); + } + + public void cancel() { + timerTask.cancel(); + } + +} diff --git a/src/main/java/com/loohp/limbo/Unsafe.java b/src/main/java/com/loohp/limbo/Unsafe.java index 8570188..28638c4 100644 --- a/src/main/java/com/loohp/limbo/Unsafe.java +++ b/src/main/java/com/loohp/limbo/Unsafe.java @@ -2,13 +2,18 @@ package com.loohp.limbo; import java.lang.reflect.Constructor; +import com.loohp.limbo.Entity.DataWatcher; +import com.loohp.limbo.Entity.Entity; +import com.loohp.limbo.Location.Location; import com.loohp.limbo.Player.Player; import com.loohp.limbo.Utils.GameMode; +import com.loohp.limbo.World.World; @Deprecated public class Unsafe { private com.loohp.limbo.Player.Unsafe playerUnsafe; + private com.loohp.limbo.World.Unsafe worldUnsafe; protected Unsafe() { try { @@ -16,6 +21,11 @@ public class Unsafe { playerConstructor.setAccessible(true); playerUnsafe = playerConstructor.newInstance(); playerConstructor.setAccessible(false); + + Constructor worldConstructor = com.loohp.limbo.World.Unsafe.class.getDeclaredConstructor(); + worldConstructor.setAccessible(true); + worldUnsafe = worldConstructor.newInstance(); + worldConstructor.setAccessible(false); } catch (Exception e) {e.printStackTrace();} } @@ -28,5 +38,20 @@ public class Unsafe { public void setPlayerEntityId(Player player, int entityId) { playerUnsafe.a(player, entityId); } + + @Deprecated + public void removeEntity(World world, Entity entity) { + worldUnsafe.a(world, entity); + } + + @Deprecated + public DataWatcher getDataWatcher(World world, Entity entity) { + return worldUnsafe.b(world, entity); + } + + @Deprecated + public void setPlayerLocationSilently(Player player, Location location) { + playerUnsafe.a(player, location); + } } diff --git a/src/main/java/com/loohp/limbo/Utils/NamespacedKey.java b/src/main/java/com/loohp/limbo/Utils/NamespacedKey.java index f89f559..9267345 100644 --- a/src/main/java/com/loohp/limbo/Utils/NamespacedKey.java +++ b/src/main/java/com/loohp/limbo/Utils/NamespacedKey.java @@ -1,6 +1,8 @@ package com.loohp.limbo.Utils; public class NamespacedKey { + + public static final String MINECRAFT_KEY = "minecraft"; private String namespace; private String key; @@ -20,6 +22,10 @@ public class NamespacedKey { this.namespace = namespace; this.key = key; } + + public static NamespacedKey minecraft(String key) { + return new NamespacedKey(MINECRAFT_KEY, key); + } public String getNamespace() { return namespace; diff --git a/src/main/java/com/loohp/limbo/Utils/Rotation3f.java b/src/main/java/com/loohp/limbo/Utils/Rotation3f.java new file mode 100644 index 0000000..e6aa345 --- /dev/null +++ b/src/main/java/com/loohp/limbo/Utils/Rotation3f.java @@ -0,0 +1,77 @@ +package com.loohp.limbo.Utils; + +public class Rotation3f { + + private double x; + private double y; + private double z; + + public Rotation3f(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(x); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Rotation3f other = (Rotation3f) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) { + return false; + } + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) { + return false; + } + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) { + return false; + } + return true; + } + +} diff --git a/src/main/java/com/loohp/limbo/World/Schematic.java b/src/main/java/com/loohp/limbo/World/Schematic.java index 7dc6ef9..3629f91 100644 --- a/src/main/java/com/loohp/limbo/World/Schematic.java +++ b/src/main/java/com/loohp/limbo/World/Schematic.java @@ -19,7 +19,7 @@ public class Schematic { //short height = nbt.getShort("Height"); byte[] blockdata = nbt.getByteArray("BlockData"); CompoundTag palette = nbt.getCompoundTag("Palette"); - ListTag blockEntities = nbt.getListTag("BlockEntities").asTypedList(CompoundTag.class); + ListTag blockEntities = nbt.containsKey("BlockEntities") ? nbt.getListTag("BlockEntities").asTypedList(CompoundTag.class) : null; Map mapping = new HashMap<>(); for (String key : palette.keySet()) { mapping.put(palette.getInt(key), key); @@ -54,19 +54,21 @@ public class Schematic { Chunk chunk = world.getChunkAtWorldPos(x, z); - Iterator itr = blockEntities.iterator(); - while (itr.hasNext()) { - CompoundTag tag = itr.next(); - int[] pos = tag.getIntArray("Pos"); - - if (pos[0] == x && pos[1] == y && pos[2] == z) { - ListTag newTag = chunk.getTileEntities(); - newTag.add(SchematicConvertionUtils.toTileEntityTag(tag)); - chunk.setTileEntities(newTag); - itr.remove(); - break; + if (blockEntities != null) { + Iterator itr = blockEntities.iterator(); + while (itr.hasNext()) { + CompoundTag tag = itr.next(); + int[] pos = tag.getIntArray("Pos"); + + if (pos[0] == x && pos[1] == y && pos[2] == z) { + ListTag newTag = chunk.getTileEntities(); + newTag.add(SchematicConvertionUtils.toTileEntityTag(tag)); + chunk.setTileEntities(newTag); + itr.remove(); + break; + } } - } + } index++; } diff --git a/src/main/java/com/loohp/limbo/World/Unsafe.java b/src/main/java/com/loohp/limbo/World/Unsafe.java new file mode 100644 index 0000000..4e524bb --- /dev/null +++ b/src/main/java/com/loohp/limbo/World/Unsafe.java @@ -0,0 +1,21 @@ +package com.loohp.limbo.World; + +import com.loohp.limbo.Entity.DataWatcher; +import com.loohp.limbo.Entity.Entity; + +@Deprecated +public class Unsafe { + + private Unsafe() {} + + @Deprecated + public void a(World a, Entity b) { + a.removeEntity(b); + } + + @Deprecated + public DataWatcher b(World a, Entity b) { + return a.getDataWatcher(b); + } + +} diff --git a/src/main/java/com/loohp/limbo/World/World.java b/src/main/java/com/loohp/limbo/World/World.java index 194b9b5..ef103db 100644 --- a/src/main/java/com/loohp/limbo/World/World.java +++ b/src/main/java/com/loohp/limbo/World/World.java @@ -1,7 +1,25 @@ package com.loohp.limbo.World; +import java.io.IOException; +import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import com.loohp.limbo.Limbo; +import com.loohp.limbo.Entity.ArmorStand; +import com.loohp.limbo.Entity.DataWatcher; +import com.loohp.limbo.Entity.DataWatcher.WatchableObject; +import com.loohp.limbo.Entity.Entity; +import com.loohp.limbo.Entity.EntityType; +import com.loohp.limbo.Location.Location; +import com.loohp.limbo.Player.Player; +import com.loohp.limbo.Server.Packets.PacketPlayOutEntityDestroy; +import com.loohp.limbo.Server.Packets.PacketPlayOutEntityMetadata; import com.loohp.limbo.Utils.SchematicConvertionUtils; import net.querz.mca.Chunk; @@ -17,6 +35,7 @@ public class World { private int length; private LightEngineBlock lightEngineBlock; private LightEngineSky lightEngineSky; + private Map entities; public World(String name, int width, int length, Environment environment) { this.name = name; @@ -52,6 +71,8 @@ public class World { if (environment.hasSkyLight()) { this.lightEngineSky = new LightEngineSky(this); } + + this.entities = new LinkedHashMap<>(); } public LightEngineBlock getLightEngineBlock() { @@ -107,6 +128,49 @@ public class World { public Chunk getChunkAtWorldPos(int x, int z) { return this.chunks[(x >> 4)][(z >> 4)]; } + + public Chunk getChunkAt(int x, int z) { + if (x < 0 || z < 0 || x >= chunks.length || z >= chunks[x].length) { + return null; + } + return this.chunks[x][z]; + } + + public int getChunkX(Chunk chunk) { + for (int x = 0; x < chunks.length; x++) { + for (int z = 0; z < chunks[x].length; z++) { + Chunk c = getChunkAt(x, z); + if (c.equals(chunk)) { + return x; + } + } + } + return Integer.MIN_VALUE; + } + + public int getChunkZ(Chunk chunk) { + for (int x = 0; x < chunks.length; x++) { + for (int z = 0; z < chunks[x].length; z++) { + Chunk c = getChunkAt(x, z); + if (c.equals(chunk)) { + return z; + } + } + } + return Integer.MIN_VALUE; + } + + public int[] getChunkXZ(Chunk chunk) { + for (int x = 0; x < chunks.length; x++) { + for (int z = 0; z < chunks[x].length; z++) { + Chunk c = getChunkAt(x, z); + if (c.equals(chunk)) { + return new int[] {x, z}; + } + } + } + return null; + } public String getName() { return name; @@ -131,6 +195,81 @@ public class World { public int getChunkLength() { return (length >> 4) + 1; } + + public Set getEntities() { + return Collections.unmodifiableSet(entities.keySet()); + } + + public Entity spawnEntity(EntityType type, Location location) { + if (!location.getWorld().equals(this)) { + throw new IllegalArgumentException("Location not in world."); + } + Entity entity; + switch (type) { + case ARMOR_STAND: + entity = new ArmorStand(location); + break; + default: + throw new UnsupportedOperationException("This EntityType cannot be summoned."); + } + entities.put(entity, new DataWatcher(entity)); + return entity; + } + + public Entity addEntity(Entity entity) { + if (entity.getWorld().equals(this)) { + entities.put(entity, new DataWatcher(entity)); + } else { + throw new IllegalArgumentException("Location not in world."); + } + return entity; + } + + public List getPlayers() { + return Limbo.getInstance().getPlayers().stream().filter(each -> each.getWorld().equals(this)).collect(Collectors.toList()); + } + + protected void removeEntity(Entity entity) { + entities.remove(entity); + PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(entity.getEntityId()); + for (Player player : getPlayers()) { + try { + player.clientConnection.sendPacket(packet); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + protected DataWatcher getDataWatcher(Entity entity) { + return entities.get(entity); + } + + public void update() throws IllegalArgumentException, IllegalAccessException { + for (DataWatcher watcher : entities.values()) { + if (watcher.getEntity().getWorld().equals(this)) { + Map updated = watcher.update(); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(watcher.getEntity(), false, updated.keySet().toArray(new Field[0])); + for (Player player : getPlayers()) { + try { + player.clientConnection.sendPacket(packet); + } catch (IOException e) { + e.printStackTrace(); + } + } + } else { + PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(watcher.getEntity().getEntityId()); + for (Player player : getPlayers()) { + try { + player.clientConnection.sendPacket(packet); + } catch (IOException e) { + e.printStackTrace(); + } + } + entities.remove(watcher.getEntity()); + } + } + } @Override public int hashCode() { diff --git a/src/main/resources/mapping.json b/src/main/resources/mapping.json index 330089b..ca0afcf 100644 --- a/src/main/resources/mapping.json +++ b/src/main/resources/mapping.json @@ -27,17 +27,21 @@ "PacketPlayOutChat": "0x0E", "PacketPlayOutPlayerAbilities": "0x30", "PacketPlayOutMapChunk": "0x20", + "PacketPlayOutUnloadChunk": "0x1C", "PacketPlayOutLightUpdate": "0x23", "PacketPlayOutKeepAlive": "0x1F", "PacketPlayOutPlayerInfo": "0x32", "PacketPlayOutUpdateViewPosition": "0x40", - "PacketPlayOutShowPlayerSkins": "0x44", "PacketPlayOutDisconnect": "0x19", "PacketPlayOutPluginMessaging": "0x17", "PacketPlayOutTabComplete": "0x0F", "PacketPlayOutDeclareCommands": "0x10", "PacketPlayOutRespawn": "0x39", - "PacketPlayOutGameState": "0x1D" + "PacketPlayOutGameState": "0x1D", + "PacketPlayOutEntityDestroy": "0x36", + "PacketPlayOutEntityMetadata": "0x44", + "PacketPlayOutSpawnEntity": "0x00", + "PacketPlayOutSpawnEntityLiving": "0x02" }, "StatusIn": { "0x01": "PacketStatusInPing", diff --git a/src/main/resources/server.properties b/src/main/resources/server.properties index bdd0132..84030f4 100644 --- a/src/main/resources/server.properties +++ b/src/main/resources/server.properties @@ -28,6 +28,12 @@ world-spawn=world;20.5;17;22.5;-90;0 #Reduce debug info reduced-debug-info=false +#The view distance of the server +view-distance=6 + +#Ticks per second of the server +ticks-per-second=5 + #Server list message in Json motd={"text":"","extra":[{"text":"Limbo Server!","color":"yellow"}]}