diff --git a/.vscode/settings.json b/.vscode/settings.json index 00a149a..557e1ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,15 +4,18 @@ "java.jdt.ls.vmargs": "-Dfile.encoding=UTF-8", "java.configuration.updateBuildConfiguration": "automatic", "cSpell.words": [ + "Gamemode", "Gamerules", "Minecraft", "Mojang", "cliffbreak", "mkdir", + "npcs", "teamcolors", "testremove", "unban", + "uninject", "varo" ], - "java.format.settings.url": "eclipse-formatter.xml", + "java.format.settings.url": "eclipse-formatter.xml" } \ No newline at end of file diff --git a/varo/src/main/java/de/cliffbreak/varo/Varo.java b/varo/src/main/java/de/cliffbreak/varo/Varo.java index 9bdb177..f413b6f 100644 --- a/varo/src/main/java/de/cliffbreak/varo/Varo.java +++ b/varo/src/main/java/de/cliffbreak/varo/Varo.java @@ -17,7 +17,7 @@ import de.cliffbreak.varo.commands.VaroCommand; import de.cliffbreak.varo.listeners.BannedItemListener; import de.cliffbreak.varo.listeners.ChatListener; import de.cliffbreak.varo.listeners.CreatureSpawnListener; -import de.cliffbreak.varo.listeners.EntityDamageByEntityListener; +import de.cliffbreak.varo.listeners.PlayerInteractNPCListener; import de.cliffbreak.varo.listeners.EntityRegainHealthListener; import de.cliffbreak.varo.listeners.PlayerClientOptionsChangeListener; import de.cliffbreak.varo.listeners.PlayerDeathListener; @@ -41,6 +41,7 @@ public class Varo extends JavaPlugin { this.playerCache = new PlayerCache(this); // this.config.addDefault("Varo.Start", "TODO: StartDate"); + this.config.addDefault("Varo.Debug", false); this.config.addDefault("Varo.Bans", new ArrayList()); this.config.options().copyDefaults(true); this.saveConfiguration(); @@ -53,13 +54,16 @@ public class Varo extends JavaPlugin { getServer().getPluginManager().registerEvents(new PlayerDeathListener(this), this); getServer().getPluginManager().registerEvents(new BannedItemListener(), this); getServer().getPluginManager().registerEvents(new CreatureSpawnListener(this), this); + getServer().getPluginManager().registerEvents(new PlayerInteractNPCListener(this), this); getCommand("varo").setExecutor(new VaroCommand(this)); for (World world : getServer().getWorlds()) { if (world.getEnvironment() == Environment.NORMAL) { - world.setDifficulty(Difficulty.HARD); + // world.setDifficulty(Difficulty.HARD); + world.setDifficulty(Difficulty.PEACEFUL); // TODO: REMOVE AFTER DEBUG!! world.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + world.setGameRule(GameRule.REDUCED_DEBUG_INFO, true); } } getServer().removeRecipe(NamespacedKey.minecraft("fishing_rod")); @@ -68,7 +72,6 @@ public class Varo extends JavaPlugin { @Override public void onDisable() { getLogger().info("Stopping CliffbreakVaro!"); - saveConfiguration(); this.playerCache.shutdown(); } diff --git a/varo/src/main/java/de/cliffbreak/varo/events/PlayerInteractNPCEvent.java b/varo/src/main/java/de/cliffbreak/varo/events/PlayerInteractNPCEvent.java new file mode 100644 index 0000000..dfb2ff4 --- /dev/null +++ b/varo/src/main/java/de/cliffbreak/varo/events/PlayerInteractNPCEvent.java @@ -0,0 +1,61 @@ +package de.cliffbreak.varo.events; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class PlayerInteractNPCEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private final Type type; + private final int id; + private final double damage; + private final boolean isCritical; + + public PlayerInteractNPCEvent(Type type, int id, double damage, boolean isCritical) { + super(true); + this.type = type; + this.id = id; + this.damage = damage; + this.isCritical = isCritical; + } + + /** + * @return InteractionType of the Event + */ + public Type getType() { + return type; + } + + /** + * @return EntityId of the Event + */ + public int getId() { + return id; + } + + /** + * @return Damage from Player to Entity (only use if Type=ATTACK) + */ + public double getDamage() { + return damage; + } + + /** + * @return Return if Player made a critical attack + */ + public boolean getIsCritical() { + return isCritical; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public enum Type { + ATTACK, INTERACT, + } +} \ No newline at end of file diff --git a/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerInteractNPCListener.java b/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerInteractNPCListener.java new file mode 100644 index 0000000..8cef25b --- /dev/null +++ b/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerInteractNPCListener.java @@ -0,0 +1,26 @@ +package de.cliffbreak.varo.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import de.cliffbreak.varo.Varo; +import de.cliffbreak.varo.events.PlayerInteractNPCEvent; +import de.cliffbreak.varo.events.PlayerInteractNPCEvent.Type; + +public class PlayerInteractNPCListener implements Listener { + + private Varo plugin; + + public PlayerInteractNPCListener(Varo plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onEntityDamageByEntity(PlayerInteractNPCEvent e) { + // plugin.getLogger().info(e.getType().toString() + " : " + e.getId() + " : " + e.getDamage()); + if (e.getType().equals(Type.ATTACK)) { + plugin.npcManager.addDamage(e.getId(), e.getDamage(), e.getIsCritical()); + } + } + +} \ No newline at end of file diff --git a/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerJoinQuitListener.java b/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerJoinQuitListener.java index b0d3519..05a8b58 100644 --- a/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerJoinQuitListener.java +++ b/varo/src/main/java/de/cliffbreak/varo/listeners/PlayerJoinQuitListener.java @@ -1,6 +1,5 @@ package de.cliffbreak.varo.listeners; -import java.io.IOException; import java.util.Stack; import com.destroystokyo.paper.event.server.PaperServerListPingEvent; @@ -15,6 +14,7 @@ import org.bukkit.scheduler.BukkitRunnable; import de.cliffbreak.varo.Varo; import de.cliffbreak.varo.uitls.MessageUtils; +import de.cliffbreak.varo.uitls.PacketReader; public class PlayerJoinQuitListener implements Listener { @@ -24,7 +24,7 @@ public class PlayerJoinQuitListener implements Listener { this.plugin = plugin; } - @EventHandler() + @EventHandler public void onPlayerQuit(final PlayerQuitEvent e) { e.setQuitMessage(null); plugin.npcManager.createClone(e.getPlayer(), true); @@ -32,51 +32,62 @@ public class PlayerJoinQuitListener implements Listener { MessageUtils.getRichTextComponent(e.getPlayer().getName(), "§f hat den Server verlassen.", true)); } - @EventHandler() + @EventHandler public void onPlayerJoin(final PlayerJoinEvent e) { e.setJoinMessage(null); plugin.npcManager.removeClone(e.getPlayer()); plugin.npcManager.syncClones(e.getPlayer()); + + // Inject PacketReader + PacketReader packetReader = new PacketReader(plugin, e.getPlayer()); + packetReader.inject(); + Bukkit.broadcast( MessageUtils.getRichTextComponent(e.getPlayer().getName(), "§f hat den Server betreten.", true)); - e.getPlayer().sendMessage("\n§7§l#### §9Cliffbreak.de - §lVaro §r§9Changelog §7§l####\n \n" - + " §cWarning: §r§c Plugin is running in DEBUG mode!\n "); - final Stack changes = new Stack(); + if (plugin.config.getBoolean("Varo.Debug")) { - changes.push(" §7• §r§lADD: §rAdd a FakePlayer if Player is logged out"); - changes.push(" §7• §r§lADD: §rAdd /varo test && /varo testremove"); - changes.push(" §7• §r§lFIX: §r/varo Command is only usable by Server Operators now"); - changes.push(" §7• §r§lADD: §rDisallow special Items (Enchanted Golden Apple, Fishing Rod, Totem of Undying)"); - changes.push(" §7• §r§lADD: §rUse Vanilla Hearts"); - changes.push(" §7• §r§lADD: §rDisable Fishing Rod Crafting Recipe"); - changes.push(" §7• §r§lFIX: §rCheck for Ban in AsyncPreLoginEvent"); - changes.push(" §7• §r§lFIX: §rOnly kick player after Entity isn't ticking anymore"); - changes.push(" §7• §r§lADD: §r/varo Command for Admins"); - changes.push(" §7• §r§lADD: §rBan Player on Death"); - changes.push(" §7• §r§lADD: §r1.8 Health Regeneration"); - changes.push(" §7• §r§lADD: §rCustom Dynamic ServerListEntry"); - changes.push(" §7• §r§lADD: §rControl Difficulty and Gamerules by the Plugin"); - changes.push(" §7• §r§lADD: §rHover over Playername for Details (Team)"); - changes.push(" §7• §r§lADD: §rRedesigned Chat with Timestamp"); + e.getPlayer().sendMessage("\n§7§l#### §9Cliffbreak.de - §lVaro §r§9Changelog §7§l####\n \n" + + " §cWarning: §r§c Plugin is running in DEBUG mode!\n "); - new BukkitRunnable() { - @Override - public void run() { - if (!e.getPlayer().isOnline()) { - this.cancel(); - return; + final Stack changes = new Stack(); + + changes.push(" §7• §r§lADD: §rFakePlayer is no attackable (can also be killed)"); + changes.push(" §7• §r§lADD: §rAdd a FakePlayer if Player is logged out"); + changes.push(" §7• §r§lADD: §rAdd /varo test && /varo testremove"); + changes.push(" §7• §r§lFIX: §r/varo Command is only usable by Server Operators now"); + changes.push( + " §7• §r§lADD: §rDisallow special Items (Enchanted Golden Apple, Fishing Rod, Totem of Undying)"); + changes.push(" §7• §r§lADD: §rUse Vanilla Hearts"); + changes.push(" §7• §r§lADD: §rDisable Fishing Rod Crafting Recipe"); + changes.push(" §7• §r§lFIX: §rCheck for Ban in AsyncPreLoginEvent"); + changes.push(" §7• §r§lFIX: §rOnly kick player after Entity isn't ticking anymore"); + changes.push(" §7• §r§lADD: §r/varo Command for Admins"); + changes.push(" §7• §r§lADD: §rBan Player on Death"); + changes.push(" §7• §r§lADD: §r1.8 Health Regeneration"); + changes.push(" §7• §r§lADD: §rCustom Dynamic ServerListEntry"); + changes.push(" §7• §r§lADD: §rControl Difficulty and Gamerules by the Plugin"); + changes.push(" §7• §r§lADD: §rHover over Playername for Details (Team)"); + changes.push(" §7• §r§lADD: §rRedesigned Chat with Timestamp"); + + new BukkitRunnable() { + @Override + public void run() { + if (!e.getPlayer().isOnline()) { + this.cancel(); + return; + } + if (changes.empty()) { + this.cancel(); + return; + } + e.getPlayer().sendMessage(changes.pop()); } - if (changes.empty()) { - this.cancel(); - return; - } - e.getPlayer().sendMessage(changes.pop()); - } - }.runTaskTimer(plugin, 20, 20); + }.runTaskTimer(plugin, 20, 20); + } } - @EventHandler() + @EventHandler public void onServerListPing(final ServerListPingEvent event) { if (event instanceof PaperServerListPingEvent) { handlePaperServerListPing((PaperServerListPingEvent) event); diff --git a/varo/src/main/java/de/cliffbreak/varo/managers/NPCManager.java b/varo/src/main/java/de/cliffbreak/varo/managers/NPCManager.java index ccfa252..05736f3 100644 --- a/varo/src/main/java/de/cliffbreak/varo/managers/NPCManager.java +++ b/varo/src/main/java/de/cliffbreak/varo/managers/NPCManager.java @@ -9,6 +9,8 @@ import com.mojang.authlib.properties.Property; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; import org.bukkit.craftbukkit.v1_15_R1.CraftServer; import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; @@ -18,7 +20,6 @@ import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; import org.json.simple.JSONArray; import org.json.simple.JSONObject; - import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; @@ -31,17 +32,18 @@ import net.minecraft.server.v1_15_R1.MinecraftServer; import net.minecraft.server.v1_15_R1.PacketPlayOutEntityDestroy; import net.minecraft.server.v1_15_R1.PacketPlayOutEntityHeadRotation; import net.minecraft.server.v1_15_R1.PacketPlayOutEntityMetadata; +import net.minecraft.server.v1_15_R1.PacketPlayOutEntityStatus; import net.minecraft.server.v1_15_R1.PacketPlayOutNamedEntitySpawn; import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo; +import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction; import net.minecraft.server.v1_15_R1.PlayerConnection; import net.minecraft.server.v1_15_R1.PlayerInteractManager; import net.minecraft.server.v1_15_R1.WorldServer; -import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction; public class NPCManager { private final Varo plugin; - private final ArrayList players = new ArrayList(); + private final ArrayList npcs = new ArrayList(); private Scoreboard scoreboard; private Team afkTeam; @@ -74,16 +76,18 @@ public class NPCManager { } catch (IOException | ParseException e) { plugin.getLogger().info(e.getMessage()); } - final EntityPlayer npc = new EntityPlayer(nmsServer, nmsWorld, gameProfile, - new PlayerInteractManager(nmsWorld)); + final PlayerInteractManager playerInteractManager = new PlayerInteractManager(nmsWorld); + final EntityPlayer npc = new EntityPlayer(nmsServer, nmsWorld, gameProfile, playerInteractManager); npc.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + npc.setHealth((float) player.getHealth()); + if (shouldHaveAFKPrefix) { this.afkTeam.addEntry(npc.getName()); } - players.add(npc); + npcs.add(npc); for (Player connectionPlayer : Bukkit.getOnlinePlayers()) { final PlayerConnection connection = ((CraftPlayer) connectionPlayer).getHandle().playerConnection; @@ -105,7 +109,7 @@ public class NPCManager { } public void syncClones(Player player) { - for (EntityPlayer npc : this.players) { + for (EntityPlayer npc : this.npcs) { final PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection; connection.sendPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, npc)); connection.sendPacket(new PacketPlayOutNamedEntitySpawn(npc)); @@ -118,10 +122,50 @@ public class NPCManager { } } + public boolean isNPC(int id) { + for (EntityPlayer npc : this.npcs) { + if (npc.getId() == id) + return true; + } + return false; + } + + public void addDamage(int id, double damage, boolean isCritical) { + for (EntityPlayer npc : this.npcs) { + if (npc.getId() == id) { + npc.setHealth((float) (npc.getHealth() - damage)); + + World world = npc.getWorld().getWorld(); + Location loc = new Location(world, npc.locX(), npc.locY(), npc.locZ()); + if (npc.getHealth() > 0.0F) { + for (Player connectionPlayer : Bukkit.getOnlinePlayers()) { + final PlayerConnection connection = ((CraftPlayer) connectionPlayer) + .getHandle().playerConnection; + connection.sendPacket(new PacketPlayOutEntityStatus(npc, (byte) 2)); // Send HURT (2) Animation + } + world.playSound(loc, Sound.ENTITY_PLAYER_HURT, 1.0F, 1.0F); + world.playSound(loc, Sound.ENTITY_PLAYER_ATTACK_STRONG, 1.0F, 1.0F); + if (isCritical) + world.playSound(loc, Sound.ENTITY_PLAYER_ATTACK_CRIT, 1.0F, 1.0F); + } else { + for (Player connectionPlayer : Bukkit.getOnlinePlayers()) { + final PlayerConnection connection = ((CraftPlayer) connectionPlayer) + .getHandle().playerConnection; + connection.sendPacket(new PacketPlayOutEntityMetadata(npc.getId(), npc.getDataWatcher(), true)); + } + if (isCritical) + world.playSound(loc, Sound.ENTITY_PLAYER_ATTACK_CRIT, 1.0F, 1.0F); + world.playSound(loc, Sound.ENTITY_PLAYER_DEATH, 1.0F, 1.0F); + } + } + + } + } + public void removeClone(Player player) { this.afkTeam.removeEntry(player.getName()); - for (int i = 0; i < this.players.size(); i++) { - EntityPlayer npc = this.players.get(i); + for (int i = 0; i < this.npcs.size(); i++) { + EntityPlayer npc = this.npcs.get(i); if (npc.getUniqueID().equals(player.getUniqueId())) { final WorldServer nmsWorld = ((CraftWorld) player.getWorld()).getHandle(); nmsWorld.removeEntity(npc); @@ -130,7 +174,7 @@ public class NPCManager { final PlayerConnection connection = ((CraftPlayer) connectionPlayer).getHandle().playerConnection; connection.sendPacket(new PacketPlayOutEntityDestroy(npc.getId())); } - this.players.remove(npc); + this.npcs.remove(npc); } } } diff --git a/varo/src/main/java/de/cliffbreak/varo/uitls/PacketReader.java b/varo/src/main/java/de/cliffbreak/varo/uitls/PacketReader.java index 058a045..bd2570c 100644 --- a/varo/src/main/java/de/cliffbreak/varo/uitls/PacketReader.java +++ b/varo/src/main/java/de/cliffbreak/varo/uitls/PacketReader.java @@ -1,23 +1,37 @@ package de.cliffbreak.varo.uitls; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; -import net.minecraft.server.v1_15_R1.Packet; - import java.lang.reflect.Field; +import java.util.Collection; import java.util.List; +import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; + +import de.cliffbreak.varo.Varo; +import de.cliffbreak.varo.events.PlayerInteractNPCEvent; +import de.cliffbreak.varo.events.PlayerInteractNPCEvent.Type; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import net.minecraft.server.v1_15_R1.AttributeModifier; +import net.minecraft.server.v1_15_R1.EnumItemSlot; +import net.minecraft.server.v1_15_R1.GenericAttributes; +import net.minecraft.server.v1_15_R1.ItemStack; +import net.minecraft.server.v1_15_R1.Packet; public class PacketReader { - Player player; - Channel channel; + private Varo plugin; + private Player player; + private Channel channel; - public PacketReader(final Player player) { + public PacketReader(final Varo plugin, final Player player) { + this.plugin = plugin; this.player = player; } @@ -40,21 +54,57 @@ public class PacketReader { } } + // https://wiki.vg/Protocol#Interact_Entity public void readPacket(final Packet packet) { if (packet.getClass().getSimpleName().equalsIgnoreCase("PacketPlayInUseEntity")) { - final int id = (Integer) getValue(packet, "a"); - System.out.println(getValue(packet, "action").toString()); - System.out.println(getValue(packet, "a").toString()); + int id = (int) getValue(packet, "a"); + if (!plugin.npcManager.isNPC(id)) { + return; + } - // if (Main.npc.getEntityID() == id) { - // if (getValue(packet, "action").toString().equalsIgnoreCase("ATTACK")) { - // Main.npc.animation(1); - // } else if (getValue(packet, "action").toString().equalsIgnoreCase("INTERACT")) { - - // } - // } + try { + Type type = Type.valueOf(getValue(packet, "action").toString()); + PlayerInteractNPCEvent event = new PlayerInteractNPCEvent(type, id, calculateDamage(this.player), + isCriticalHit(this.player)); + Bukkit.getServer().getPluginManager().callEvent(event); + } catch (Exception e) { + // noop() + } } + + } + + private double calculateDamage(Player player) { + double damage = 1.0; // Default 1.0 Damage (by hand) + ItemStack nmsItemStack = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand()); + Collection attributes = nmsItemStack.getItem().a(EnumItemSlot.MAINHAND) + .get(GenericAttributes.ATTACK_DAMAGE.getName()); + + for (AttributeModifier am : attributes) { + damage += am.getAmount(); + } + + if (isCriticalHit(player)) { + damage = damage * 1.5; + } + + damage = damage * player.getAttackCooldown(); + + Map enchantments = player.getInventory().getItemInMainHand().getEnchantments(); + + for (Map.Entry enchantment : enchantments.entrySet()) { + // TODO: Add more enchantments? + if (enchantment.getKey().equals(Enchantment.DAMAGE_ALL)) + damage += 0.5 * (enchantment.getValue() + 1); + } + return damage; + } + + private boolean isCriticalHit(Player player) { + return player.getFallDistance() > 0.0F && !player.isOnGround() && !player.getLocation().getBlock().isLiquid() + && player.getPotionEffect(PotionEffectType.BLINDNESS) == null && player.getVehicle() == null + && !player.isSprinting() && player.getAttackCooldown() > 0.848F; } public void setValue(final Object obj, final String name, final Object value) {