I'm back again.
Today I have a question that many people have asked before. The reason I'm asking again is because in all my ~90 minutes of searching, I couldn't find an updated answer. Many answers tell me to use iTag/TagAPI, but I ran into some problems trying to use that, therefore I would not like to use iTag/TagAPI. I'm trying to use packets, and I found one answer, but it too was outdated.
EntityPlayer entityP = ((CraftPlayer) p).getHandle();
entityP.displayName = args[0];
for (Player a: Bukkit.getOnlinePlayers()) {
if (!p.getUniqueId().equals(a.getUniqueId()))
((CraftPlayer) a).getHandle().playerConnection.sendPacket(new PacketPlayOutNamedEntitySpawn(entityP));
}
Here's that thread I was going off of: https://bukkit.org/threads/change-player-name-above-head.162356/
Any help is appreciated!
It is possible to achieve this in 1.8. For convenience, I used ProtocolLib and PacketWrapper.
Since the 1.8 update, the NamedEntitySpawn packet has been modified and changing player's name by modifying that has been no longer supported.(ref)
But this post gave a reference: we can use packet PlayerInfoData. I did some testing, and here's the result(tested against 1.9.2):
Here's the code:
Player theGuyToChangeNameFor = Bukkit.getPlayer("theguy");
PlayerInfoData pid = new PlayerInfoData(WrappedGameProfile.fromPlayer(theGuyToChangeNameFor), 1,
EnumWrappers.NativeGameMode.SURVIVAL,
WrappedChatComponent.fromText("whatever_string"));
WrapperPlayServerPlayerInfo wpspi = new WrapperPlayServerPlayerInfo();
wpspi.setAction(EnumWrappers.PlayerInfoAction.REMOVE_PLAYER);
wpspi.setData(Collections.singletonList(pid));
for(Player p : Bukkit.getOnlinePlayers())
{
if(p.equals(theGuyToChangeNameFor))
{
continue;
}
p.hidePlayer(theGuyToChangeNameFor);
wpspi.sendPacket(p);
}
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(this, PacketType.Play.Server.PLAYER_INFO)
{
#Override
public void onPacketSending(PacketEvent event)
{
if(event.getPacket().getPlayerInfoAction().read(0) != EnumWrappers.PlayerInfoAction.ADD_PLAYER)
{
return;
}
PlayerInfoData pid = event.getPacket().getPlayerInfoDataLists().read(0).get(0);
if(!pid.getProfile().getName().toLowerCase().equals("theguy")) // Here you can do something to ensure you're changing the name of the correct guy
{
return;
}
PlayerInfoData newPid = new PlayerInfoData(pid.getProfile().withName("HEAD_NAME"), pid.getPing(), pid.getGameMode(),
WrappedChatComponent.fromText("TAB_LIST_NAME"));
event.getPacket().getPlayerInfoDataLists().write(0, Collections.singletonList(newPid));
}
}
);
for(Player p : Bukkit.getOnlinePlayers())
{
if(p.equals(theGuyToChangeNameFor))
{
continue;
}
p.showPlayer(theGuyToChangeNameFor);
}
Explanation:
We use ProtocolLib to modify the PlayerInfoData packets from the server to change the player's display name. (You can see that name tag and tab list name can even be two different values!)
hidePlayer, showPlayer and REMOVE_PLAYER are used to refresh the player's name immediately(otherwise it will require logging out and in again). So far haven't found a better method. If you have one, say it:)
Related
I was trying to make a function which displays info about the server.
public static void serverInfo(Guild guild, MessageChannel channel) {
EmbedBuilder embed = new EmbedBuilder();
//Calculations
int people = 0;
int roles = 0;
int tc = 0;
int vc = 0;
for (Member member : guild.getMembers()) {
if (!member.getUser().isBot())
++people;
}
for (Role ignored : guild.getRoles())
++roles;
for (TextChannel ignored : guild.getTextChannels())
++tc;
for (VoiceChannel ignored : guild.getVoiceChannels())
++vc;
String time = String.valueOf(guild.getTimeCreated());
String created = time.substring(8, 10) + "-" + time.substring(5, 7) + "-" + time.substring(0, 4);
embed.setTitle(guild.getName());
embed.setThumbnail(guild.getIconUrl());
embed.addField("Total Members", String.valueOf(guild.getMemberCount()+1), true);
embed.addField("Members", String.valueOf(people),true);
embed.addField("Bots", String.valueOf((guild.getMemberCount()+1)-people), true);
embed.addField("Owner", Objects.requireNonNull(guild.getOwner()).getUser().getName(), true);
embed.addField("Roles", String.valueOf(roles), true);
embed.addField("Text Channels", String.valueOf(tc), false);
embed.addField("Voice Channels", String.valueOf(vc), true);
embed.addField("Date Created", created, false);
channel.sendMessageEmbeds(embed.build()).queue();
}
However, this raises a NullPointerException
java.lang.NullPointerException at
java.base/java.util.Objects.requireNonNull(Objects.java:208) at
com.television.Commands.Infos.serverInfo(Infos.java:38) at
com.television.CommandExecutor.onMessageReceived(CommandExecutor.java:19)
But, if I removed this part from the function, it works just fine, and no exception is raised.
for (Member member : guild.getMembers()) {
if (!member.getUser().isBot())
++people;
}
Why does this happen? This problem also gets raised only in 1 server, out of the 3 servers I've tested in.
And, secondly, I know this is not much related to the question from the title, how can I calculate the number of members/bots because this part (the for-each loop in the code snippet above) does not calculate the number of members correctly, it always has 1 as the value of the bot variable, and therefore number of members - 1 is the value of people.
Two things in advance: You have to either cache all the members from every guild or instead (recommended) retrieve them when needed. To be able to do so, you need to enable the GUILD_MEMBERS Privileged Intent.
You can pretty easily retrieve a List representing all members of a guild with the following method:
public CompletableFuture<List<Member>> loadMembersFull(Guild guild) {
CompletableFuture<List<Member>> future = new CompletableFuture<>();
if (guild.isLoaded()) {
future.complete(guild.getMembers());
} else {
guild.loadMembers()
.onError(future::completeExceptionally)
.onSuccess(future::complete);
}
}
With that you can then move on with all your other stuff.
I actually don't know why it would work without the for-loop, but it looks like the error does not occur there, but when loading the owner, as it throws the exception in your #requireNonNull.
The owner object is null when he/she is no longer in the guild or not yet loaded. The owner could also have deleted the account or get banned by Discord.
To also solve this problem, I recommend you to replace your line with the following one:
embed.addField("Owner", Optional.ofNullable(guild.getOwner()).map(owner -> owner.getUser().getName()).orElse("<not found>"), true);
To get the proper amount of users, you should filter the list of users for whether they are bots or not.
int amount = (int) loadMembersFull(guild).join().stream()
.map(Member::getUser)
.filter(user -> !user.isBot())
.count();
If you need more help, feel free to ask me on my Discord server
i try making a plugin where i need to display heads on a GUI, i found these heads on minecraft-heads in the custom heads section, but those aren't normal player's head , they're stocked in a plugin, head database, so i can't obtain them with a SkullMeta#setOwner("Player"). My question is: how can i obtain those heads ?
To know: In the website i can obtain them without the head database plugin, but the commands are like: /give #p skull 1 3 {display:{Name:"Earth"},SkullOwner:{Id:"e3ae97fb-b688-4dfd-8ee6-247790f22ecd",Properties:{textures:[{Value:"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTI4OWQ1YjE3ODYyNmVhMjNkMGIwYzNkMmRmNWMwODVlODM3NTA1NmJmNjg1YjVlZDViYjQ3N2ZlODQ3MmQ5NCJ9fX0="}]}}} so i have the possibility to place a NPC with the skin, then obtain his head, or /give the item to the player then change it position but that isn't anyway optimized.
Can you help me ?
Edit: i have to access to the head URL
Edit: One of my friends found the solution using the URL, here is it:
public static void setHead(ItemMeta im, String playerSkullUrl) {
GameProfile profile = new GameProfile(UUID.randomUUID(), null);
byte[] encodedData = Base64.getEncoder().encode(String.format("{textures:{SKIN:{url:\"%s\"}}}", playerSkullUrl).getBytes());
profile.getProperties().put("textures", new Property("textures", new String(encodedData)));
Field profileField = null;
try {
profileField = im.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(im, profile);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e1) {
e1.printStackTrace();
}
}
That's all ^^
First, the HeadDatabase resource contains heads with skins that contain newly registered players, and therefore also created skins. There is no need to have the HeadDatabase API available, as you can easily retrieve heads with skins using the Bukkit API.
To be able to retrieve heads, you need the SkullMeta interface, which provides the necessary properties to call the correct head.
An example code how to retrieve a head using Bukkit API:
ItemMeta itemMeta = new ItemStack(Material.PLAYER_HEAD).getItemMeta();
if (itemMeta instanceof SkullMeta) {
return; // This check is not necessary, but for safe cast usually better
}
SkullMeta meta = (SkullMeta) itemMeta;
OfflinePlayer player = ...; // Get one of offline player using Bukkit#getOfflinePlayer
// Version check above 1.12+
if (Version.isCurrentHigher(Version.v1_12_R1)) {
meta.setOwningPlayer(player); // Player can be online or offline
} else {
meta.setOwner(player.getName()); // SkullMeta#setOwner is deprecated
}
If you use Paper API, you could use Player#setPlayerProfile and set the existing profile to this Player object.
If you already have an existing skin and UUID, like you mentioned the Properties you can use SessionServer from Mojang and retrieving player informations as a Json object. You could use my workaround for this, which is thread safe.
I am trying to complete a programming task where I load a URL with information on the 2012 NFL team players. I am using a Hashmap (playerIndfo) where the key is 'team' and the values is an arraylist of all the 'PlayerData' (playersInfo). Currently I have written:
if(playerInfo.containsKey(team)) {
playerInfo.get(team).add(player);
}
else {
playersInfo.add(player);
playerInfo.put(team, playersInfo);
}
This seems to add every player to every team, proven by if I attempt to print out information for a certain key, every player is listed. Any help would be appreciated!
I think you have the same playersInfo list for all players. You should create a new one for each team.
if (playerInfo.containsKey(team)) {
playerInfo.get(team).add(player);
} else {
List<Player> playersInfo = new ArrayList<>();
playersInfo.add(player);
playerInfo.put(team, playersInfo);
}
if(playerInfo.containsKey(team)) {
playerInfo.get(team).add(player);
}
else {
List<PlayerData> playersInfo = new ArrayList<>();
playersInfo.add(player);
playerInfo.put(team, playersInfo);
}
I have two questions about Java Convention. I try to make use od Robert C. Martin's "Clean Code".
Following case:
public void startProgressIfAllowed() {
try {
tryStartProgressIfAllowed();
} catch (Exception exception) {
// log error
}
}
private void tryStartProgressIfAllowed() {
if (isStartProgressAllowed()) {
stopProgressOnCurrentlyStartedTask();
startProgressOnThisTask();
}
}
private boolean isStartProgressAllowed() {
// Calls JOptionPane.showConfirmDialog with JOptionPane.YES_NO_OPTION.
// Created dialog contains checkbox indicating that saving currently started task is required.
// returns boolean depending on JOptionPane.YES_NO_OPTION clicked button
}
private void stopProgressOnCurrentlyStartedTask() {
// Saves currently started task depending on checkbox selecion property and stops currently started.
// What is the correct way to get checkbox selecion property?
}
Proposed solution:
public void tryStartProgressIfAllowed() {
if (tryToStopProgressOnStartedTaskIfNecessary()) {
startProgressOnThisTask();
}
}
private boolean tryToStopProgressOnStartedTaskIfNecessary() {
// Calls JOptionPane.showConfirmDialog with JOptionPane.YES_NO_OPTION.
// Created dialog contains checkbox indicating that saving currently started task is required.
// Depending on checkbox selecion property saves task.
// returns boolean depending on JOptionPane.YES_NO_OPTION clicked button
}
But this approach doesn't meet the "Command Query Separation" principle, because tryToStopProgressOnStartedTaskIfNecessary(...) method performs some logic and returns success/failure value.
I think this approach also doesn't meet the "One level of abstraction per function" principle, because I suppose "check" and "save" operations are on different levels of abstraction.
Is the method name correct to avoid disinformation? Maybe better name would be tryToStopProgressAndSaveStartedTaskIfNecessary(...)?
Is there any better solution for above problem?
What about the following:
public void tryStartProgressOnThisTaskIfAllowed() {
tryStopTaskInProgressIfAllowed()
if (!isTaskInProgress()) {
tryStartProgressOnThisTask();
}
}
private void tryStopTaskInProgressIfAllowed() {
if (!isTaskInProgress()) {
return;
}
TaskInProgressResult result = whatToDoWithTaskInProgress();
if (result == Result.KEEP) {
return;
} else if (result == Result.DROP)
tryDropTaskInProgress();
} else if (result == Result.SAVE) {
trySaveTaskInProgress();
}
}
About your points:
You now have two separate methods for C and Q
I think the two things whatToDoWithTaskInProgress and tryDropTaskInProgress are the same level of abstraction. If you'd inline the code of one or the other you were absolutely right of course.
I changed some of the method names according to my taste :) The only thing I still don't like is the part "OnThisTask" because this task is somewhat meaningless. Maybe it's only because the rest of the code is unknown maybe OnNextTask or OnNewTask are better.
The problem we were having is that we were thinking in UI terms YES/NO + checkbox value. But it is much better to think in business terms here. I identified three different outcomes that are of interest: KEEP, SAVE, DROP How the answer is obtained should not matter to the calling method.
This seems something to ask on CodeReview, see the drop down at the top left of the page.
An example of how such stateliness is realized in Java SE: the regex Matcher class.
String s = ...
Pattern pattern = Pattern.compile("...");
Matcher m = pattern.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, ... m.group(1) ...);
}
m.appendTail(sb);
with m.matches() and m.lookingAt as alternative circuits too.
In short state is held in a processing class on the actual data (String here).
Im developing a system that stores courses that participants can apply to.
I'm presenting the enrollments in a JTree in the courseadministratorGUI.
My problem is that, for every enrollment it's adding a new courseNode.
Been trying for many hours, and hope I can now get some advice that will point me in the correct direction.
Thank you.
private void updateJTree() {
for (Category cat : catcontrol.getAllCategoriesList()) {
category = new DefaultMutableTreeNode(cat);
for (Course c : ccontrol.getAllCourses()) {
if (cat.getIdCategory() == c.getIdCategory()) {
for (Enrollment e : econtrol.getAllEnrollments()) {
if (e.getIdCourse() == c.getIdCourse()) {
if (cat.getIdCategory() == c.getIdCategory() && e.getCourse().equals(c)) {
root.add(category);
}
if (c.getIdCourse() == e.getIdCourse()) {
course = new DefaultMutableTreeNode(c);
category.add(course);
enrollment = new DefaultMutableTreeNode(e.getParticipant().getFirstNames());
course.add(enrollment);
}
}
}
}
}
}
jTree1.setModel(new DefaultTreeModel(root));
jTree1.addTreeSelectionListener(this);
jTree1.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
I am not sure I understand the question, but... why "for every enrollment it's adding a new courseNode"? Because you're telling it to. Right here:
course = new DefaultMutableTreeNode(c);
category.add(course);
enrollment = new DefaultMutableTreeNode(e.getParticipant().getFirstNames());
course.add(enrollment);
Add course, then add an enrollment. Always in pairs. Now I don't know the structure of your data, but I dare say that is not what you want. Can you describe in more detail what Enrollment is (one person? list of people? something else?), and what getParticipant() returns, and what do you want to appear in the tree?
One more comment - you do realise that these two if statements check the same thing, right? You can get rid of the inner one, since it's not doing anything. Or, more likely, rewrite to do something useful (however, as I said, I am not too sure what it is you want to do).
if (e.getIdCourse() == c.getIdCourse()) {
// ...
if (c.getIdCourse() == e.getIdCourse()) {