Solved [HELP] NPC doesn't delete sometimes...

Discussion in 'Spigot Plugin Development' started by Nimblebiter, Jun 13, 2021.

  1. I'm creating a plugin where it mimics the idea of 'freecam'.

    The basic understanding of it is:
    1. do command.
    2. changes game mode from survival to spectator.
    3. spawn NPC in the exact location (w/ same skin and rotation of player).
    4. stores location where did command.
    5. (eventually, if you do the command again - you teleport back to where you first did the command, change game mode back to survival, and removes the NPC.)

      Currently, everything works, except for the part where it removes the NPC after you do the command for a second time - sometimes...

      If a player does the steps ^above^ it works as intended.
      If another player leaves and joins while watching the NPC, it works as intended.
      However, if the player who DID the command leaves & joins whilst in the spectator state - it won't delete the NPC once doing the command again (teleporting and game mode-changing works though).

      I have no idea what the issue is, I originally thought that it was because it would send packets when the NPC was created, and the player rejoining - however, I have disproved that.

      Code (Java):

      package me.bobby.playerdeathprotect;

      import java.util.HashMap;
      import java.util.Map;
      import java.util.UUID;

      import org.bukkit.Bukkit;
      import org.bukkit.craftbukkit.v1_16_R3.CraftServer;
      import org.bukkit.craftbukkit.v1_16_R3.CraftWorld;
      import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
      import org.bukkit.entity.Player;
      import com.mojang.authlib.GameProfile;
      import com.mojang.authlib.properties.Property;

      import net.md_5.bungee.api.ChatColor;
      import net.minecraft.server.v1_16_R3.DataWatcherObject;
      import net.minecraft.server.v1_16_R3.DataWatcherRegistry;
      import net.minecraft.server.v1_16_R3.EntityPlayer;
      import net.minecraft.server.v1_16_R3.MinecraftServer;
      import net.minecraft.server.v1_16_R3.PacketPlayOutEntityDestroy;
      import net.minecraft.server.v1_16_R3.PacketPlayOutEntityHeadRotation;
      import net.minecraft.server.v1_16_R3.PacketPlayOutEntityMetadata;
      import net.minecraft.server.v1_16_R3.PacketPlayOutNamedEntitySpawn;
      import net.minecraft.server.v1_16_R3.PacketPlayOutPlayerInfo;
      import net.minecraft.server.v1_16_R3.PlayerConnection;
      import net.minecraft.server.v1_16_R3.PlayerInteractManager;
      import net.minecraft.server.v1_16_R3.WorldServer;

      public class NPC {
         
          private static HashMap<Player, EntityPlayer> LAST = new HashMap<Player, EntityPlayer>();
         
          public static void createNPC(Player player, String skin, boolean boo) {
              MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
              WorldServer world = ((CraftWorld) Bukkit.getWorld(player.getWorld().getName())).getHandle();
              GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ChatColor.GOLD + "" + ChatColor.BOLD + "[SPECTATOR]");
              EntityPlayer npc = new EntityPlayer(server, world, gameProfile, new PlayerInteractManager(world));
                  npc.setLocation(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ(), player.getLocation().getYaw(), player.getLocation().getPitch());
                 
                  String[] name = getSkin(player, skin);
                  gameProfile.getProperties().put("textures", new Property("textures", name[0], name[1]));
                  npc.getDataWatcher().set(new DataWatcherObject<>(16, DataWatcherRegistry.a), (byte)127);
                 
                  addNPCPacket(npc);
                  LAST.put(player, npc);
          }
         
          private static String[] getSkin(Player player, String name) {
                  EntityPlayer p = ((CraftPlayer) player).getHandle();
                  GameProfile profile = p.getProfile();
                  Property property = profile.getProperties().get("textures").iterator().next();
                  String texture = property.getValue();
                  String signature = property.getSignature();
                  return new String[] {texture, signature};
          }
         
          public static void addNPCPacket(EntityPlayer npc) {
              for (Player player : Bukkit.getOnlinePlayers()) {
                  PlayerConnection connection = ((CraftPlayer)player).getHandle().playerConnection;
                  connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, npc));
                  connection.sendPacket(new PacketPlayOutNamedEntitySpawn(npc));
                  connection.sendPacket(new PacketPlayOutEntityHeadRotation(npc, (byte) (npc.yaw * 256 / 360)));
                  connection.sendPacket(new PacketPlayOutEntityMetadata(npc.getId(), npc.getDataWatcher(), true));
              }
          }
         
          public static void addJoinPacket(Player player) {
              for (Map.Entry<Player, EntityPlayer> entry : LAST.entrySet()) {
                  EntityPlayer key = entry.getValue();
                      PlayerConnection connection = ((CraftPlayer)player).getHandle().playerConnection;
                      connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, key));
                      connection.sendPacket(new PacketPlayOutNamedEntitySpawn(key));
                      connection.sendPacket(new PacketPlayOutEntityHeadRotation(key, (byte) (key.yaw * 256 / 360)));
                      connection.sendPacket(new PacketPlayOutEntityMetadata(key.getId(), key.getDataWatcher(), true));
                  }
              }
         
          public static void destroy(Player p) {
              if (LAST.containsKey(p)) {
                  for (Player player : Bukkit.getOnlinePlayers()) {
                      EntityPlayer key = LAST.get(p);
                      PlayerConnection connection = ((CraftPlayer)player).getHandle().playerConnection;
                      connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, key));
                      connection.sendPacket(new PacketPlayOutEntityDestroy(key.getId()));
                          }
                  LAST.remove(p);
                      }
                  }

          public static HashMap<Player, EntityPlayer> getNPCs() {
              return LAST;
          }
      }


       

      Code (Java):

      package me.bobby.playerdeathprotect.commands;

      import java.util.HashMap;
      import java.util.Map;

      import org.bukkit.ChatColor;
      import org.bukkit.GameMode;
      import org.bukkit.Location;
      import org.bukkit.command.Command;
      import org.bukkit.command.CommandExecutor;
      import org.bukkit.command.CommandSender;
      import org.bukkit.entity.Player;

      import hu.montlikadani.setthespawn.SpawnHandler;
      import me.bobby.playerdeathprotect.NPC;

      public class Gamemode implements CommandExecutor {

          Map<String, Location> locations = new HashMap<String, Location>();
          Map<String, Long> cooldowns = new HashMap<String, Long>();

          public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
              Player player = (Player) sender;

              if (!(sender instanceof Player)) {
                  sender.sendMessage("Sorry Console, Can't Change Gamemode ;(");
                  return true;
              }
              if (sender instanceof Player) {
                  if (sender.hasPermission("destinite.mode")) {
                      if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.ADVENTURE) {
                          sender.sendMessage(ChatColor.RED + "Sorry, you are in " + ChatColor.RED + player.getGameMode()
                                  + ChatColor.RED + ", you must be in SPECTATOR or SURVIVAL to use that commmand.");
                          return true;
                      }
                      if (!(cooldowns.containsKey(player.getName()))) { // first time player does command
                          cooldowns.put(player.getName(), System.currentTimeMillis());
                          if (player.getGameMode() == GameMode.SURVIVAL) {
                              locations.put(player.getName(), player.getLocation());
                              player.setGameMode(GameMode.SPECTATOR);
                              NPC.createNPC(player, player.getName());
                              Both(player);
                              return true;
                          } else if (player.getGameMode() == GameMode.SPECTATOR) {
                              org.bukkit.Location spawn = SpawnHandler.getLocation(player.getWorld()); // API to get spawn location
                              if (spawn != null) {
                                  player.teleport(spawn);
                                  player.sendMessage(ChatColor.RED+ "Sorry, we couldn't find a previous location - teleporting to spawn.");
                                  player.setGameMode(GameMode.SURVIVAL);
                                  Both(player);
                                  return true;
                              } else if (spawn == null) {
                                  player.setGameMode(GameMode.SURVIVAL);
                                  player.sendMessage(ChatColor.RED+ "Sorry, we couldn't find a previous location or spawn - changing gamemode.");
                                  cooldowns.put(player.getName(), System.currentTimeMillis() + (10 * 1000));
                                  return true;
                              }
                          }
                          return true;
                      }
                      if (cooldowns.containsKey(player.getName())) {
                          if (cooldowns.get(player.getName()) > System.currentTimeMillis()) {
                              Long timeleft = (cooldowns.get(player.getName()) - System.currentTimeMillis()) / 1000;
                              player.sendMessage(ChatColor.GOLD + "Please wait " + ChatColor.WHITE + timeleft + ChatColor.GOLD+ " seconds.");
                              return true;
                          }
                          if (cooldowns.get(player.getName()) <= System.currentTimeMillis()) {
                              if (player.getGameMode() == GameMode.SURVIVAL) {
                                  locations.put(player.getName(), player.getLocation());
                                  player.setGameMode(GameMode.SPECTATOR);
                                  NPC.createNPC(player, player.getName());
                                  Both(player);
                                  return true;
                              } else if (player.getGameMode() == GameMode.SPECTATOR) {
                                  player.teleport(locations.get(player.getName()));
                                  NPC.destroy(player);
                                  player.setGameMode(GameMode.SURVIVAL);
                                  Both(player);
                                  return true;
                              }
                          }
                      }
                  } else if (!(sender.hasPermission("destinite.mode"))) {
                      sender.sendMessage(ChatColor.RED + "I'm sorry, but you do not have permission to perform this command. Please contact the server administrator if you believe that this is in error.");
                      return true;
                  }
              }
              return true;
          }
         
         
          void Both(Player player) {
              ConsoleWarning(player);
              cooldowns.put(player.getName(), System.currentTimeMillis() + (10 * 1000)); // gets system time, +10 seconds to what time can use command next
          }
         
          void ConsoleWarning(Player player) {
              System.out.println(ChatColor.YELLOW + "[Destinite]: " + player.getName() + " has left freecam mode, ignore '" + player.getName() + " moved too quickly!' warnings.");
          }
      }


       

      Code (Java):
      @EventHandler
          public void onJoining(PlayerJoinEvent j) {
              if ((NPC.getNPCs().isEmpty() || NPC.getNPCs().size() == 0) || NPC.getNPCs().containsKey(j.getPlayer())) { return; }
              else { NPC.addJoinPacket(j.getPlayer()); }
          }
     
    #1 Nimblebiter, Jun 13, 2021
    Last edited: Jun 13, 2021
  2. I seemed to have fixed it.

    In the HashMap I changed the key from 'Player' to 'String'. (setting the String to player.getName())

    I have no idea why but I assume when you rejoin a server it sets it to something else??? idk
     
  3. The reason is, that whenever a player joins a new instance of CraftPlayer is created representing that player. It will not be the same object, which is why your map will not contain the player.

    You should consider using UUID instead of the playername, it's better practice and techncially the player could have changed their name while offline.
     
  4. Thanks for the advice, you prove a good point - I will change it to UUID. ty