Add GUI framework for class selection in CTF

Introduced a GUI system to facilitate class selection in the CTF game. This includes an abstract GUIInventory, GUIListener for event handling, and a ClassSelectionGUI for selecting classes. Also, added the "selectclass" command and integrated the GUI with game mechanics for improved player interaction.
This commit is contained in:
Teriuihi 2025-02-07 23:22:50 +01:00
parent 6a9721d0e7
commit 73c11ab1ca
11 changed files with 253 additions and 11 deletions

View File

@ -10,6 +10,7 @@ import com.alttd.ctf.events.OnSnowballHit;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.flag.FlagTryCaptureEvent;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.gui.GUIListener;
import com.alttd.ctf.json_config.JacksonConfig;
import com.alttd.ctf.json_config.JsonConfigManager;
import com.alttd.ctf.team.Team;
@ -69,6 +70,7 @@ public class Main extends JavaPlugin {
pluginManager.registerEvents(new FlagTryCaptureEvent(flag), this);
pluginManager.registerEvents(new OnPlayerDeath(gameManager), this);
pluginManager.registerEvents(new OnPlayerJoin(gameManager, flag), this);
pluginManager.registerEvents(new GUIListener(), this);
}
private void registerTeams() {

View File

@ -1,10 +1,7 @@
package com.alttd.ctf.commands;
import com.alttd.ctf.Main;
import com.alttd.ctf.commands.subcommands.ChangeTeam;
import com.alttd.ctf.commands.subcommands.CreateTeam;
import com.alttd.ctf.commands.subcommands.Reload;
import com.alttd.ctf.commands.subcommands.Start;
import com.alttd.ctf.commands.subcommands.*;
import com.alttd.ctf.config.Messages;
import com.alttd.ctf.flag.Flag;
import com.alttd.ctf.game.GameManager;
@ -39,6 +36,7 @@ public class CommandManager implements CommandExecutor, TabExecutor {
new ChangeTeam(gameManager),
new Start(gameManager, flag),
new CreateTeam(main, gameManager),
new SelectClass(gameManager),
new Reload(main)
);
}

View File

@ -0,0 +1,70 @@
package com.alttd.ctf.commands.subcommands;
import com.alttd.ctf.commands.SubCommand;
import com.alttd.ctf.config.Messages;
import com.alttd.ctf.game.GameManager;
import com.alttd.ctf.game_class.GameClass;
import com.alttd.ctf.game_class.creation.FighterCreator;
import com.alttd.ctf.gui.ClassSelectionGUI;
import com.alttd.ctf.team.TeamPlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
public class SelectClass extends SubCommand {
private final GameManager gameManager;
private final HashMap<Integer, List<GameClass>> gameClasses;
public SelectClass(GameManager gameManager) {
this.gameManager = gameManager;
this.gameClasses = new HashMap<>();
gameManager.getTeams().forEach(team -> {
gameClasses.computeIfAbsent(team.getId(), teamId -> new ArrayList<>()).add(FighterCreator.createFighter(team.getColor()));
});
}
@Override
public int onCommand(CommandSender commandSender, String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
return -1;
}
if (gameManager.getGamePhase().isEmpty()) {
commandSender.sendRichMessage("<red>CTF has to be running to select a class.</red>");
return 0;
}
Optional<TeamPlayer> optionalTeamPlayer = gameManager.getTeamPlayer(player.getUniqueId());
if (optionalTeamPlayer.isEmpty()) {
commandSender.sendRichMessage("<red>You have to be in a CTF team to select a class.</red>");
return 0;
}
TeamPlayer teamPlayer = optionalTeamPlayer.get();
if (teamPlayer.getTeam().getSpawnLocation().distance(player.getLocation()) > 15) {
commandSender.sendRichMessage("<red>You have to be near your spawn to change classes.</red>");
return 0;
}
new ClassSelectionGUI(gameClasses.get(teamPlayer.getTeam().getId()), teamPlayer)
.open(player);
return 0;
}
@Override
public String getName() {
return "selectclass";
}
@Override
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
return List.of();
}
@Override
public String getHelpMessage() {
return Messages.HELP.SELECT_CLASS;
}
}

View File

@ -26,6 +26,7 @@ public class Messages extends AbstractConfig {
public static String CHANGE_TEAM = "<green>Change a players team: <gold>/ctf changeteam <player> <team></gold></green>";
public static String CREATE_TEAM = "<green>Create a team: <gold>/ctf createteam <team_name> <hex_color></gold></green>";
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
public static String SELECT_CLASS = "<green>Open class selection: <gold>/ctf selectclass</gold></green>";
@SuppressWarnings("unused")
private static void load() {
@ -35,6 +36,7 @@ public class Messages extends AbstractConfig {
CHANGE_TEAM = config.getString(prefix, "change-team", CHANGE_TEAM);
CREATE_TEAM = config.getString(prefix, "create-team", CREATE_TEAM);
START = config.getString(prefix, "start", START);
SELECT_CLASS = config.getString(prefix, "select-class", SELECT_CLASS);
}
}

View File

@ -4,6 +4,8 @@ import com.alttd.ctf.team.TeamColor;
import com.alttd.ctf.team.TeamPlayer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Material;
@ -13,6 +15,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -21,7 +24,10 @@ public abstract class GameClass {
private final List<Material> armor;
private final List<ItemStack> tools;
@Getter
private final ItemStack displayItem;
@Getter
private final Component className;
private final double health;
//Delay in ticks between throws (prevents auto clickers)
@Getter
@ -30,7 +36,7 @@ public abstract class GameClass {
@Getter
private final int damage;
protected GameClass(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int throwTickSpeed, int damage) {
protected GameClass(@NotNull List<Material> armor, @NotNull List<ItemStack> tools, @NotNull ItemStack displayItem, double health, int throwTickSpeed, int damage) {
if (armor.size() != 4) {
throw new IllegalArgumentException("Armor has to have 4 entries");
}
@ -40,6 +46,11 @@ public abstract class GameClass {
this.health = health;
this.throwTickSpeed = throwTickSpeed;
this.damage = damage;
ItemMeta itemMeta = displayItem.getItemMeta();
if (itemMeta == null) {
throw new IllegalArgumentException("Display item has no item meta");
}
this.className = itemMeta.displayName();
}
public void apply(TeamPlayer teamPlayer) {
@ -64,6 +75,8 @@ public abstract class GameClass {
player.updateInventory();
teamPlayer.setGameClass(this);
player.sendRichMessage("You selected the <class_name> class", Placeholder.component("class_name", className));
player.teleportAsync(teamPlayer.getTeam().getSpawnLocation());
}
private void setArmor(Player player, int r, int g, int b) {
@ -78,10 +91,6 @@ public abstract class GameClass {
}).toArray(ItemStack[]::new));
}
ItemStack getDisplayItem() {
return displayItem;
}
public void setArmor(Player player, TeamPlayer teamPlayer) {
TeamColor color = teamPlayer.getTeam().getColor();
setArmor(player, color.r(), color.g(), color.b());

View File

@ -0,0 +1,29 @@
package com.alttd.ctf.gui;
import com.alttd.ctf.game_class.GameClass;
import com.alttd.ctf.team.TeamPlayer;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ClassSelectionGUI extends GUIInventory {
public ClassSelectionGUI(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer) {
super(InventoryType.CHEST, teamPlayer.getTeam().getName().append(MiniMessage.miniMessage().deserialize(" - class selection")));
createClassSelection(gameClasses, teamPlayer);
}
private void createClassSelection(@NotNull List<GameClass> gameClasses, @NotNull TeamPlayer teamPlayer) {
int pos = (9 + (9 - gameClasses.size()) / 2);
for (GameClass gameClass : gameClasses) {
setItem(pos++, gameClass.getDisplayItem(), player -> {
gameClass.apply(teamPlayer);
player.closeInventory(InventoryCloseEvent.Reason.PLUGIN);
});
}
}
}

View File

@ -0,0 +1,19 @@
package com.alttd.ctf.gui;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.MerchantInventory;
import java.util.HashMap;
import java.util.UUID;
public interface GUI {
HashMap<UUID, GUI> GUIByUUID = new HashMap<>();
void open(Player player);
GUIAction getGuiAction(int slot);
Inventory getInventory();
}

View File

@ -0,0 +1,7 @@
package com.alttd.ctf.gui;
import org.bukkit.entity.Player;
public interface GUIAction {
void click(Player player);
}

View File

@ -0,0 +1,46 @@
package com.alttd.ctf.gui;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.MerchantInventory;
import java.util.HashMap;
public abstract class GUIInventory implements GUI {
@Getter
protected final Inventory inventory;
protected final HashMap<Integer, GUIAction> guiActions;
public GUIInventory(InventoryType type, Component name) {
inventory = Bukkit.createInventory(null, type, name);
guiActions = new HashMap<>();
}
public void setItem(int slot, ItemStack stack, GUIAction action) {
this.inventory.setItem(slot, stack);
if (action != null) {
guiActions.put(slot, action);
}
}
public void setItem(int slot, ItemStack stack) {
setItem(slot, stack, null);
}
public void open(Player player) {
player.openInventory(inventory);
GUIByUUID.put(player.getUniqueId(), this);
}
public GUIAction getGuiAction(int slot) {
return guiActions.get(slot);
}
}

View File

@ -0,0 +1,60 @@
package com.alttd.ctf.gui;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
public class GUIListener implements Listener {
/**
* Handles clicking inside a gui
* @param event gui click event
*/
@EventHandler
public void onClick(InventoryClickEvent event){
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
GUI gui = GUI.GUIByUUID.get(player.getUniqueId());
if (gui == null) {
return;
}
if (gui.getInventory() != null) {
if (!gui.getInventory().equals(event.getInventory())) {
return;
}
} else {
return;
}
event.setCancelled(true);
Inventory clickedInventory = event.getClickedInventory();
if (clickedInventory == null || clickedInventory.getType().equals(InventoryType.PLAYER)) {
return;
}
GUIAction action = gui.getGuiAction(event.getSlot());
if (action != null) {
action.click(player);
}
}
@EventHandler
public void onClose(InventoryCloseEvent event) {
GUI.GUIByUUID.remove(event.getPlayer().getUniqueId());
}
@EventHandler
public void onQuit(PlayerQuitEvent event){
GUI.GUIByUUID.remove(event.getPlayer().getUniqueId());
}
}

View File

@ -1,3 +1,3 @@
#Fri Feb 07 22:07:29 CET 2025
buildNumber=5
#Fri Feb 07 23:09:43 CET 2025
buildNumber=8
version=0.1