diff --git a/src/main/java/betterquesting/client/gui2/party/GuiPartyManage.java b/src/main/java/betterquesting/client/gui2/party/GuiPartyManage.java index 57e51c3c3..6c3a4d555 100644 --- a/src/main/java/betterquesting/client/gui2/party/GuiPartyManage.java +++ b/src/main/java/betterquesting/client/gui2/party/GuiPartyManage.java @@ -43,8 +43,8 @@ import net.minecraft.nbt.NBTTagCompound; import org.lwjgl.input.Keyboard; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; public class GuiPartyManage extends GuiScreenCanvas implements IPEventListener, INeedsRefresh { private IParty party; @@ -56,6 +56,13 @@ public GuiPartyManage(GuiScreen parent) { super(parent); } + // 0 = INVITE, 1 = MEMBER, 2 = ADMIN, 3 = OWNER/OP + private EnumPartyStatus getStatus(UUID id) { + if (NameCache.INSTANCE.isOP(id)) return EnumPartyStatus.OWNER; + EnumPartyStatus perm = party.getStatus(id); + return perm == null ? EnumPartyStatus.MEMBER : perm; // Fallback (potentially exploitable I know) + } + @Override public void refreshGui() { UUID playerID = QuestingAPI.getQuestingUUID(mc.player); @@ -94,9 +101,7 @@ public void initPanel() { PEventBroadcaster.INSTANCE.register(this, PEventButton.class); Keyboard.enableRepeatEvents(true); - // 0 = INVITE, 1 = MEMBER, 2 = ADMIN, 3 = OWNER/OP - EnumPartyStatus status = NameCache.INSTANCE.isOP(playerID) ? EnumPartyStatus.OWNER : party.getStatus(playerID); - if (status == null) status = EnumPartyStatus.MEMBER; // Fallback (potentially exploitable I know) + EnumPartyStatus status = getStatus(playerID); // Background panel CanvasTextured cvBackground = new CanvasTextured(new GuiTransform(GuiAlign.FULL_BOX, new GuiPadding(0, 0, 0, 0), 0), PresetTexture.PANEL_MAIN.getTexture()); @@ -140,10 +145,6 @@ public void initPanel() { CanvasEmpty cvRightHalf = new CanvasEmpty(new GuiTransform(GuiAlign.HALF_RIGHT, new GuiPadding(8, 32, 16, 32), 0)); cvBackground.addPanel(cvRightHalf); - PanelTextBox txInvite = new PanelTextBox(new GuiTransform(GuiAlign.TOP_EDGE, new GuiPadding(0, 0, 0, -16), 0), QuestTranslation.translate("betterquesting.gui.party_members")).setAlignment(1); - txInvite.setColor(PresetColor.TEXT_HEADER.getColor()); - cvRightHalf.addPanel(txInvite); - CanvasScrolling cvUserList = new CanvasScrolling(new GuiTransform(GuiAlign.FULL_BOX, new GuiPadding(0, 16, 8, 0), 0)); cvRightHalf.addPanel(cvUserList); @@ -153,44 +154,71 @@ public void initPanel() { scUserList.getTransform().setParent(cvUserList.getTransform()); cvUserList.setScrollDriverY(scUserList); - List partyMemList = party.getMembers(); int elSize = RenderUtils.getStringWidth("...", fontRenderer); int cvWidth = cvUserList.getTransform().getWidth(); boolean hardcore = QuestSettings.INSTANCE.getProperty(NativeProps.HARDCORE); ItemTexture txHeart = new ItemTexture(new BigItemStack(BetterQuesting.extraLife)); - for (int i = 0; i < partyMemList.size(); i++) { - UUID mid = partyMemList.get(i); - String mName = NameCache.INSTANCE.getName(mid); + int heightOffset = 0; - if (RenderUtils.getStringWidth(mName, fontRenderer) > cvWidth - 58) { - mName = mc.fontRenderer.trimStringToWidth(mName, cvWidth - 58 - elSize) + "..."; - } + // Iterate through the statuses in descending order + for (EnumPartyStatus statusEntry : Arrays.asList(EnumPartyStatus.OWNER, EnumPartyStatus.ADMIN, EnumPartyStatus.MEMBER)) { + List members = party.getMembers().stream().filter(id -> getStatus(id).equals(statusEntry)).collect(Collectors.toList()); - PanelPlayerPortrait pnPortrait = new PanelPlayerPortrait(new GuiRectangle(0, i * 32, 32, 32, 0), mid, mName); - cvUserList.addPanel(pnPortrait); + if (members.isEmpty()) continue; - PanelTextBox txMemName = new PanelTextBox(new GuiRectangle(32, i * 32 + 4, cvWidth - 32, 12, 0), mName); - txMemName.setColor(PresetColor.TEXT_MAIN.getColor()); - cvUserList.addPanel(txMemName); + String statusNameKey = switch (statusEntry) { + case OWNER -> "betterquesting.gui.party_owner"; + case ADMIN -> "betterquesting.gui.party_admins"; + case MEMBER -> "betterquesting.gui.party_members"; + }; - PanelButtonStorage btnKick = new PanelButtonStorage<>(new GuiRectangle(cvWidth - 32, i * 32, 32, 32, 0), 3, QuestTranslation.translate("betterquesting.btn.party_kick"), mName); - cvUserList.addPanel(btnKick); + PanelTextBox txStatusName = new PanelTextBox(new GuiRectangle(0, heightOffset, cvWidth, 16, 0), QuestTranslation.translate(statusNameKey)).setAlignment(1); + txStatusName.setColor(PresetColor.TEXT_HEADER.getColor()); + cvUserList.addPanel(txStatusName); - PanelGeneric pnItem = new PanelGeneric(new GuiRectangle(32, i * 32 + 16, 16, 16, 0), txHeart); - cvUserList.addPanel(pnItem); + heightOffset += 16; - String lifeCount; + for (UUID id : members) { + String username = NameCache.INSTANCE.getName(id); + boolean displayKickButton = statusEntry.ordinal() < status.ordinal() && playerID != id; + boolean displayAdminButton = displayKickButton && status == EnumPartyStatus.OWNER; - if (hardcore) { - lifeCount = " x " + LifeDatabase.INSTANCE.getLives(mid); - } else { - lifeCount = " x \u221E"; - } + PanelPlayerPortrait pnPortrait = new PanelPlayerPortrait(new GuiRectangle(0, heightOffset, 32, 32, 0), id, username); + cvUserList.addPanel(pnPortrait); + + int maxAllowedWidth = cvWidth - 32; + if (displayKickButton) maxAllowedWidth -= 32; + if (displayAdminButton) maxAllowedWidth -= 48; - PanelTextBox txLives = new PanelTextBox(new GuiRectangle(48, i * 32 + 20, cvWidth - 48 - 32, 12, 0), lifeCount); - txLives.setColor(PresetColor.TEXT_MAIN.getColor()); - cvUserList.addPanel(txLives); + String shortenedName = RenderUtils.getStringWidth(username, fontRenderer) > maxAllowedWidth ? fontRenderer.trimStringToWidth(username, maxAllowedWidth - elSize) + "..." : username; + PanelTextBox txMemName = new PanelTextBox(new GuiRectangle(32, heightOffset + 4, cvWidth - 32, 12, 0), shortenedName); + txMemName.setColor(PresetColor.TEXT_MAIN.getColor()); + cvUserList.addPanel(txMemName); + + if (displayKickButton) { + PanelButtonStorage btnKick = new PanelButtonStorage<>(new GuiRectangle(cvWidth - 32, heightOffset, 32, 32, 0), 3, QuestTranslation.translate("betterquesting.btn.party_kick"), username); + cvUserList.addPanel(btnKick); + + if (displayAdminButton) { + var rect = new GuiRectangle(cvWidth - 80, heightOffset, 48, 32, 0); + if (statusEntry == EnumPartyStatus.MEMBER) { + cvUserList.addPanel(new PanelButtonStorage<>(rect, 5, QuestTranslation.translate("betterquesting.btn.party_promote"), username)); + } else { + cvUserList.addPanel(new PanelButtonStorage<>(rect, 6, QuestTranslation.translate("betterquesting.btn.party_demote"), username)); + } + } + } + + PanelGeneric pnItem = new PanelGeneric(new GuiRectangle(32, heightOffset + 16, 16, 16, 0), txHeart); + cvUserList.addPanel(pnItem); + + PanelTextBox txLives = new PanelTextBox(new GuiRectangle(48, heightOffset + 20, cvWidth - 48 - 32, 12, 0), " x " + (hardcore ? LifeDatabase.INSTANCE.getLives(id) : "∞")); + txLives.setColor(PresetColor.TEXT_MAIN.getColor()); + cvUserList.addPanel(txLives); + + heightOffset += 32; + } } scUserList.setActive(cvUserList.getScrollBounds().getHeight() > 0); @@ -238,6 +266,22 @@ private void onButtonPress(PEventButton event) { payload.setInteger("partyID", partyID); payload.setTag("data", party.writeProperties(new NBTTagCompound())); NetPartyAction.sendAction(payload); + } else if (btn.getButtonID() == 5 && btn instanceof PanelButtonStorage) // Toggle permission level of user between ADMIN and MEMBER + { + String id = ((PanelButtonStorage) btn).getStoredValue(); + NBTTagCompound payload = new NBTTagCompound(); + payload.setInteger("action", 6); + payload.setInteger("partyID", partyID); + payload.setString("username", id); + NetPartyAction.sendAction(payload); + } else if (btn.getButtonID() == 6 && btn instanceof PanelButtonStorage) // Toggle permission level of user between ADMIN and MEMBER + { + String id = ((PanelButtonStorage) btn).getStoredValue(); + NBTTagCompound payload = new NBTTagCompound(); + payload.setInteger("action", 7); + payload.setInteger("partyID", partyID); + payload.setString("username", id); + NetPartyAction.sendAction(payload); } } } diff --git a/src/main/java/betterquesting/network/handlers/NetPartyAction.java b/src/main/java/betterquesting/network/handlers/NetPartyAction.java index 41a423378..8a304c493 100644 --- a/src/main/java/betterquesting/network/handlers/NetPartyAction.java +++ b/src/main/java/betterquesting/network/handlers/NetPartyAction.java @@ -25,7 +25,6 @@ import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -import org.apache.logging.log4j.Level; import java.util.UUID; @@ -46,16 +45,17 @@ public static void sendAction(NBTTagCompound payload) { } private static void onServer(Tuple message) { + NBTTagCompound compound = message.getFirst(); EntityPlayerMP sender = message.getSecond(); - int action = !message.getFirst().hasKey("action", 99) ? -1 : message.getFirst().getInteger("action"); - int partyID = !message.getFirst().hasKey("partyID", 99) ? -1 : message.getFirst().getInteger("partyID"); + int action = !compound.hasKey("action", 99) ? -1 : compound.getInteger("action"); + int partyID = !compound.hasKey("partyID", 99) ? -1 : compound.getInteger("partyID"); IParty party = PartyManager.INSTANCE.getValue(partyID); int permission = party == null ? 0 : checkPermission(QuestingAPI.getQuestingUUID(sender), party); switch (action) { case 0: { - createParty(sender, message.getFirst().getString("name")); + createParty(sender, compound.getString("name")); break; } case 1: { @@ -65,12 +65,12 @@ private static void onServer(Tuple message) { } case 2: { if (permission < 2) break; - editParty(partyID, party, message.getFirst().getCompoundTag("data")); + editParty(partyID, party, compound.getCompoundTag("data")); break; } case 3: { if (permission < 2) break; - inviteUser(partyID, message.getFirst().getString("username"), message.getFirst().getLong("expiry")); + inviteUser(partyID, compound.getString("username"), compound.getLong("expiry")); break; } case 4: { @@ -78,15 +78,30 @@ private static void onServer(Tuple message) { break; } case 5: { - kickUser(partyID, sender, party, message.getFirst().getString("username"), permission); + kickUser(partyID, sender, party, compound.getString("username"), permission); + break; + } + case 6: { + promotePlayer(partyID, party, compound.getString("username"), permission); + break; + } + case 7: { + demotePlayer(partyID, party, compound.getString("username"), permission); break; } default: { - BetterQuesting.logger.log(Level.ERROR, "Invalid party action '" + action + "'. Full payload:\n" + message.getFirst().toString()); + BetterQuesting.logger.error("Invalid party action '" + action + "'. Full payload:\n" + compound); } } } + private static UUID getUUID(String username) { + MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + EntityPlayerMP player = server.getPlayerList().getPlayerByUsername(username); + UUID uuid = QuestingAPI.getQuestingUUID(player); + return uuid == null ? NameCache.INSTANCE.getUUID(username) : uuid; + } + private static void createParty(EntityPlayerMP sender, String name) { UUID playerID = QuestingAPI.getQuestingUUID(sender); if (PartyManager.INSTANCE.getParty(playerID) != null) return; @@ -114,19 +129,18 @@ private static void editParty(int partyID, IParty party, NBTTagCompound settings } private static void inviteUser(int partyID, String username, long expiry) { - UUID uuid = null; - MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); - EntityPlayerMP player = server.getPlayerList().getPlayerByUsername(username); - if (player != null) uuid = QuestingAPI.getQuestingUUID(player); - if (uuid == null) uuid = NameCache.INSTANCE.getUUID(username); - if (uuid != null) { - PartyInvitations.INSTANCE.postInvite(uuid, partyID, expiry); - if (player != null) { - NetPartySync.sendSync(new EntityPlayerMP[]{player}, new int[]{partyID}); - NetInviteSync.sendSync(player); - } - } else { - BetterQuesting.logger.error("Unable to identify " + username + " to invite to party " + partyID); // No idea who this is + UUID uuid = getUUID(username); + if (uuid == null) { + BetterQuesting.logger.error("Unable to identify " + username + " to invite to party " + partyID); + return; // No idea who this is + } + PartyInvitations.INSTANCE.postInvite(uuid, partyID, expiry); + // despite what the annotations claim, this can return null + var player = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayerByUUID(uuid); + //noinspection ConstantValue + if (player != null) { + NetPartySync.sendSync(new EntityPlayerMP[]{player}, new int[]{partyID}); + NetInviteSync.sendSync(player); } } @@ -136,7 +150,7 @@ private static void acceptInvite(int partyID, EntityPlayerMP sender) { if (party != null) return; if (PartyInvitations.INSTANCE.acceptInvite(playerID, partyID)) { NetPartySync.quickSync(partyID); - NetNameSync.quickSync(sender, partyID); + NetNameSync.quickSync(null, partyID); } else { BetterQuesting.logger.error("Invalid invite for " + sender.getName() + " to party " + partyID); } @@ -150,11 +164,7 @@ private static void kickUser(int partyID, EntityPlayerMP sender, IParty party, S return; } - UUID uuid = null; - MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); - EntityPlayerMP player = server.getPlayerList().getPlayerByUsername(username); - if (player != null) uuid = QuestingAPI.getQuestingUUID(player); - if (uuid == null) uuid = NameCache.INSTANCE.getUUID(username); + UUID uuid = getUUID(username); if (uuid == null) { BetterQuesting.logger.error("Unable to identify " + username + " to remove them from party " + partyID); return; // No idea who this is @@ -167,6 +177,9 @@ private static void kickUser(int partyID, EntityPlayerMP sender, IParty party, S if (party.getMembers().size() > 0) { NetPartySync.quickSync(partyID); + // despite what the annotations claim, this can return null + var player = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayerByUUID(uuid); + //noinspection ConstantValue if (player != null) { NBTTagCompound payload = new NBTTagCompound(); payload.setInteger("action", 5); @@ -188,6 +201,54 @@ private static void kickUser(int partyID, EntityPlayerMP sender, IParty party, S } } + private static void promotePlayer(int partyID, IParty party, String username, int permission) { + if (permission < EnumPartyStatus.ADMIN.ordinal()) { + BetterQuesting.logger.error("Tried to promote a player, but didn't have a high enough permission to do so!"); + return; + } + if (party == null) { + BetterQuesting.logger.error("Tried to increase a player's permission level of a non-existent party (" + partyID + ")"); + return; + } + + UUID uuid = getUUID(username); + if (uuid == null) { + BetterQuesting.logger.error("Unable to identify " + username + " to promote them in " + partyID); + return; // No idea who this is + } + + if (checkPermission(uuid, party) < permission) { + party.setStatus(uuid, EnumPartyStatus.ADMIN); + NetPartySync.quickSync(partyID); + } else { + BetterQuesting.logger.error("Insufficient permissions to increase the status level of " + username + " from party " + partyID); + } + } + + private static void demotePlayer(int partyID, IParty party, String username, int permission) { + if (permission < EnumPartyStatus.OWNER.ordinal()) { + BetterQuesting.logger.error("Tried to demote a player, but didn't have a high enough permission to do so!"); + return; + } + if (party == null) { + BetterQuesting.logger.error("Tried to decrease a player's permission level of a non-existent party (" + partyID + ")"); + return; + } + + UUID uuid = getUUID(username); + if (uuid == null) { + BetterQuesting.logger.error("Unable to identify " + username + " to demote them in " + partyID); + return; // No idea who this is + } + + if (checkPermission(uuid, party) < permission) { + party.setStatus(uuid, EnumPartyStatus.MEMBER); + NetPartySync.quickSync(partyID); + } else { + BetterQuesting.logger.error("Insufficient permissions to decrease the status level of " + username + " from party " + partyID); + } + } + private static int checkPermission(UUID playerID, IParty party) { MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); EntityPlayerMP player = server == null ? null : server.getPlayerList().getPlayerByUUID(playerID); diff --git a/src/main/resources/assets/betterquesting/lang/en_us.lang b/src/main/resources/assets/betterquesting/lang/en_us.lang index bde9a5d2b..b3d8335fb 100644 --- a/src/main/resources/assets/betterquesting/lang/en_us.lang +++ b/src/main/resources/assets/betterquesting/lang/en_us.lang @@ -53,6 +53,8 @@ betterquesting.btn.party_new=Create Party betterquesting.btn.party_leave=Leave betterquesting.btn.party_invite=Invite betterquesting.btn.party_kick=Kick +betterquesting.btn.party_promote=Promote +betterquesting.btn.party_demote=Demote betterquesting.btn.party_join=Join betterquesting.btn.party_share_lives=Share Lives betterquesting.btn.party_share_loot=Single Reward @@ -83,6 +85,8 @@ betterquesting.gui.search=Search betterquesting.gui.folder=Folder betterquesting.gui.party_invites=Invites betterquesting.gui.party_members=Members +betterquesting.gui.party_admins=Admins +betterquesting.gui.party_owner=Owner betterquesting.gui.remaining_lives=Remaining Lives: %s betterquesting.gui.full_lives=Lives Full betterquesting.gui.closing_warning=Are you sure you want to close this editor?