/teams and /nopvp changes etc.e etc.
This commit is contained in:
parent
4326c1d7bd
commit
7fcaaa3221
12 changed files with 1275 additions and 15 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,6 +2,9 @@
|
||||||
.gradle/
|
.gradle/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
# run-paper test server
|
||||||
|
run/
|
||||||
|
|
||||||
# Gradle wrapper jar must stay versioned
|
# Gradle wrapper jar must stay versioned
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -5,20 +5,30 @@ A quality-of-life SMP plugin for Paper/Folia.
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Join messages** — a "Welcome to the SMP" title on every join, friendly welcome messages (including a first-join message that lists the server's plugins and a warning that `/nopvp` abuse is bannable and that combat logging is a bad idea), and a special announcement when the owner logs in.
|
- **Join messages** — a "Welcome to the SMP" title on every join, friendly welcome messages (including a first-join message that lists the server's plugins and a warning that `/nopvp` abuse is bannable and that combat logging is a bad idea), and a special announcement when the owner logs in.
|
||||||
- **`/nopvp`** — toggle no-PvP mode. While active you cannot damage other players and other players cannot damage you (direct hits, projectiles, pets, lingering potions, and player-deployed explosives such as TNT, TNT minecarts, end crystals and bed bombs). While enabled you also glow so everyone can see you're protected. To stop the immunity being used offensively, protected players (and anyone within **25 blocks** of one) are also blocked from placing, priming or detonating PvP explosives — including redstone-primed TNT whose placer is remembered, and dispensers near a protected player firing TNT/lava/fire charges. No-PvP also can't be switched on while you're in combat. Turning no-PvP *off* starts a **1-hour cooldown** before you can enable it again, and the state persists across restarts so relogging can't bypass it.
|
- **`/nopvp`** — toggle no-PvP mode. While active you cannot damage other players and other players cannot damage you (direct hits, projectiles, pets, lingering potions, and player-deployed explosives such as TNT, TNT minecarts, end crystals and bed bombs). While enabled a floating **"Pacifist"** label appears above you and, if you're in a team with glow on, you glow in the team's colour (visible to everyone). To stop the immunity being used offensively, protected players (and anyone within **25 blocks** of one) are also blocked from placing, priming or detonating PvP explosives — including redstone-primed TNT whose placer is remembered, and dispensers near a protected player firing TNT/lava/fire charges. No-PvP also can't be switched on while you're in combat. Turning no-PvP *off* starts a **1-hour cooldown** before you can enable it again, and the state persists across restarts so relogging can't bypass it.
|
||||||
- **Anti-combat-logging** — whenever you hit another player or get hit by one, you're tagged as being in combat for **15 seconds** with a chat warning. Logging off while tagged kills you on the spot and announces it to the server.
|
- **Anti-combat-logging** — whenever you hit another player or get hit by one, you're tagged as being in combat for **15 seconds** with a chat warning. Logging off while tagged kills you on the spot and announces it to the server.
|
||||||
|
- **Cosmetic teams** — `/team create/delete/invite/leave/join/color/admin/glow/chat`. Teams are purely cosmetic (friendly fire stays on). The owner runs the show and can promote **admins** who may also invite players and change the colour; membership is request-based rather than open. A team's colour becomes the glow colour of any member who has no-PvP enabled (toggleable with `/team glow`, on by default), and `/team chat` gives the team a private chat channel. Stored in `plugins/qol-tweaks/teams.yml`.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
| Command | Description | Permission |
|
| Command | Description | Permission |
|
||||||
|----------|--------------------------------------------------------------------------|-----------------------|
|
|--------------------------------|----------------------------------------------------------------------|-------------------|
|
||||||
| `/nopvp` | Toggle no-PvP mode (damage immunity vs. players + player glow). | `qoltweaks.nopvp` |
|
| `/nopvp` | Toggle no-PvP mode ("Pacifist" label + optional team-coloured glow). | `qoltweaks.nopvp` |
|
||||||
|
| `/team create <name>` | Create a team (you become its owner). | `qoltweaks.team` |
|
||||||
|
| `/team invite <p>` | Invite an online player, or accept their join request (owner/admin). | `qoltweaks.team` |
|
||||||
|
| `/team join <team>` | Accept an invite, or send a join request to the team. | `qoltweaks.team` |
|
||||||
|
| `/team color <clr>` | Set the team glow colour, or `none` to clear (owner/admin). | `qoltweaks.team` |
|
||||||
|
| `/team admin <add\|remove> <p>`| Promote/demote a member to admin (owner only). | `qoltweaks.team` |
|
||||||
|
| `/team glow` | Toggle the whole team's no-PvP glow (owner/admin). | `qoltweaks.team` |
|
||||||
|
| `/team chat [message]` | Send a team-only message, or toggle team chat mode. | `qoltweaks.team` |
|
||||||
|
| `/team leave` | Leave your team (owners must delete instead). | `qoltweaks.team` |
|
||||||
|
| `/team delete` | Disband the whole team (owner only). | `qoltweaks.team` |
|
||||||
|
|
||||||
All permissions default to `true`.
|
All permissions default to `true`. Note `/team` takes over the vanilla scoreboard-team command.
|
||||||
|
|
||||||
## Data
|
## Data
|
||||||
|
|
||||||
Player no-PvP state and cooldown timestamps are stored in `plugins/qol-tweaks/nopvp.yml` so they survive restarts and reloads.
|
Player no-PvP state and cooldown timestamps are stored in `plugins/qol-tweaks/nopvp.yml` so they survive restarts and reloads. Teams, ownership, admins, members, colours and the glow toggle are stored in `plugins/qol-tweaks/teams.yml`.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPCommand;
|
||||||
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPListener;
|
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPListener;
|
||||||
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPManager;
|
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPManager;
|
||||||
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPProtectionListener;
|
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPProtectionListener;
|
||||||
|
import com.dvorakmortonxd.qoltweaks.teams.TeamCommand;
|
||||||
|
import com.dvorakmortonxd.qoltweaks.teams.TeamListener;
|
||||||
|
import com.dvorakmortonxd.qoltweaks.teams.TeamManager;
|
||||||
import org.bukkit.command.PluginCommand;
|
import org.bukkit.command.PluginCommand;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
|
@ -14,14 +17,17 @@ public final class QoLtweaks extends JavaPlugin {
|
||||||
|
|
||||||
private NoPvPManager noPvPManager;
|
private NoPvPManager noPvPManager;
|
||||||
private CombatLogManager combatLogManager;
|
private CombatLogManager combatLogManager;
|
||||||
|
private TeamManager teamManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Plugin startup logic
|
// Plugin startup logic
|
||||||
noPvPManager = new NoPvPManager(this);
|
teamManager = new TeamManager(this);
|
||||||
|
noPvPManager = new NoPvPManager(this, teamManager);
|
||||||
combatLogManager = new CombatLogManager(this);
|
combatLogManager = new CombatLogManager(this);
|
||||||
|
|
||||||
getServer().getPluginManager().registerEvents(new JoinListener(), this);
|
getServer().getPluginManager().registerEvents(new JoinListener(), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new TeamListener(teamManager), this);
|
||||||
getServer().getPluginManager().registerEvents(new NoPvPListener(noPvPManager), this);
|
getServer().getPluginManager().registerEvents(new NoPvPListener(noPvPManager), this);
|
||||||
getServer().getPluginManager().registerEvents(new NoPvPProtectionListener(noPvPManager), this);
|
getServer().getPluginManager().registerEvents(new NoPvPProtectionListener(noPvPManager), this);
|
||||||
getServer().getPluginManager().registerEvents(new CombatLogListener(this, combatLogManager), this);
|
getServer().getPluginManager().registerEvents(new CombatLogListener(this, combatLogManager), this);
|
||||||
|
|
@ -30,14 +36,25 @@ public final class QoLtweaks extends JavaPlugin {
|
||||||
if (noPvPCommand != null) {
|
if (noPvPCommand != null) {
|
||||||
noPvPCommand.setExecutor(new NoPvPCommand(noPvPManager, combatLogManager));
|
noPvPCommand.setExecutor(new NoPvPCommand(noPvPManager, combatLogManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PluginCommand teamCommand = getCommand("team");
|
||||||
|
if (teamCommand != null) {
|
||||||
|
TeamCommand executor = new TeamCommand(teamManager, noPvPManager);
|
||||||
|
teamCommand.setExecutor(executor);
|
||||||
|
teamCommand.setTabCompleter(executor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
// Plugin shutdown logic
|
// Plugin shutdown logic
|
||||||
if (noPvPManager != null) {
|
if (noPvPManager != null) {
|
||||||
|
noPvPManager.removeAllVisuals();
|
||||||
noPvPManager.save();
|
noPvPManager.save();
|
||||||
}
|
}
|
||||||
|
if (teamManager != null) {
|
||||||
|
teamManager.saveNow();
|
||||||
|
}
|
||||||
if (combatLogManager != null) {
|
if (combatLogManager != null) {
|
||||||
combatLogManager.cancelNotices();
|
combatLogManager.cancelNotices();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class JoinListener implements Listener {
|
||||||
player.sendMessage("§7- tradecycle, which lets you cycle thru villagers trades by pressing [F] in any inventory slot while trading");
|
player.sendMessage("§7- tradecycle, which lets you cycle thru villagers trades by pressing [F] in any inventory slot while trading");
|
||||||
player.sendMessage("§7- ClickVillagers, which lets you pick up villagers into your inventory by shift-right-clicking");
|
player.sendMessage("§7- ClickVillagers, which lets you pick up villagers into your inventory by shift-right-clicking");
|
||||||
player.sendMessage("§7- a /nopvp command to toggle no-pvp mode if you want to build in peace");
|
player.sendMessage("§7- a /nopvp command to toggle no-pvp mode if you want to build in peace");
|
||||||
|
player.sendMessage("§7- cosmetic /team commands to make a team with friends and pick your glow colour");
|
||||||
player.sendMessage("§cAbuse of /nopvp is bannable. Also, combat logging is NOT a good idea.");
|
player.sendMessage("§cAbuse of /nopvp is bannable. Also, combat logging is NOT a good idea.");
|
||||||
player.sendMessage("§aThats about it, enjoy your game! :3");
|
player.sendMessage("§aThats about it, enjoy your game! :3");
|
||||||
event.setJoinMessage("§e" + player.getName() + " has joined the SMP for the first time!");
|
event.setJoinMessage("§e" + player.getName() + " has joined the SMP for the first time!");
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
import org.bukkit.event.entity.EntityDamageEvent;
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
|
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
|
||||||
public final class NoPvPListener implements Listener {
|
public final class NoPvPListener implements Listener {
|
||||||
|
|
@ -29,12 +32,27 @@ public final class NoPvPListener implements Listener {
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
manager.applyGlow(event.getPlayer());
|
manager.applyState(event.getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||||
manager.applyGlow(event.getPlayer());
|
manager.applyState(event.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
|
||||||
|
manager.applyState(event.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
manager.clearVisuals(event.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||||
|
manager.clearVisuals(event.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.dvorakmortonxd.qoltweaks.nopvp;
|
package com.dvorakmortonxd.qoltweaks.nopvp;
|
||||||
|
|
||||||
|
import com.dvorakmortonxd.qoltweaks.teams.TeamManager;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.configuration.ConfigurationSection;
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
|
@ -21,13 +22,17 @@ public final class NoPvPManager {
|
||||||
public static final double PROTECTION_RADIUS = 25.0;
|
public static final double PROTECTION_RADIUS = 25.0;
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
|
private final TeamManager teamManager;
|
||||||
private final File dataFile;
|
private final File dataFile;
|
||||||
|
private final PacifistHologram hologram;
|
||||||
private final Map<UUID, Boolean> enabled = new ConcurrentHashMap<>();
|
private final Map<UUID, Boolean> enabled = new ConcurrentHashMap<>();
|
||||||
private final Map<UUID, Long> cooldownUntil = new ConcurrentHashMap<>();
|
private final Map<UUID, Long> cooldownUntil = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public NoPvPManager(JavaPlugin plugin) {
|
public NoPvPManager(JavaPlugin plugin, TeamManager teamManager) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.teamManager = teamManager;
|
||||||
this.dataFile = new File(plugin.getDataFolder(), "nopvp.yml");
|
this.dataFile = new File(plugin.getDataFolder(), "nopvp.yml");
|
||||||
|
this.hologram = new PacifistHologram(plugin);
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +61,7 @@ public final class NoPvPManager {
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
enabled.put(uuid, true);
|
enabled.put(uuid, true);
|
||||||
cooldownUntil.put(uuid, 0L);
|
cooldownUntil.put(uuid, 0L);
|
||||||
player.setGlowing(true);
|
applyState(player);
|
||||||
scheduleSave();
|
scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,16 +69,39 @@ public final class NoPvPManager {
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
enabled.put(uuid, false);
|
enabled.put(uuid, false);
|
||||||
cooldownUntil.put(uuid, System.currentTimeMillis() + COOLDOWN_MILLIS);
|
cooldownUntil.put(uuid, System.currentTimeMillis() + COOLDOWN_MILLIS);
|
||||||
player.setGlowing(false);
|
applyState(player);
|
||||||
scheduleSave();
|
scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyGlow(Player player) {
|
/**
|
||||||
|
* Applies the no-PvP visuals for a player: the Pacifist hologram plus, if
|
||||||
|
* they are in a team with glow enabled, the team-coloured glow.
|
||||||
|
*/
|
||||||
|
public void applyState(Player player) {
|
||||||
if (isEnabled(player)) {
|
if (isEnabled(player)) {
|
||||||
player.setGlowing(true);
|
hologram.show(player);
|
||||||
|
player.setGlowing(teamManager != null && teamManager.shouldGlow(player));
|
||||||
|
} else {
|
||||||
|
hologram.hide(player);
|
||||||
|
player.setGlowing(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Removes the per-player visuals, e.g. on quit or death. */
|
||||||
|
public void clearVisuals(Player player) {
|
||||||
|
hologram.hide(player);
|
||||||
|
player.setGlowing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Re-applies visuals on the player's own region thread (Folia safe). */
|
||||||
|
public void refreshLater(Player player) {
|
||||||
|
player.getScheduler().run(plugin, task -> applyState(player), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAllVisuals() {
|
||||||
|
hologram.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
if (!dataFile.exists()) {
|
if (!dataFile.exists()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.dvorakmortonxd.qoltweaks.nopvp;
|
||||||
|
|
||||||
|
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.TextDisplay;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a floating "Pacifist" label above a no-PvP player. The label is a
|
||||||
|
* non-persistent {@link TextDisplay} that follows its player via that player's
|
||||||
|
* Folia entity scheduler, so it stays on the owning region thread.
|
||||||
|
*/
|
||||||
|
public final class PacifistHologram {
|
||||||
|
|
||||||
|
private static final String TAG = "qol_pacifist";
|
||||||
|
/** Blocks above the player's feet; ~1 block above the head, one line above the name tag. */
|
||||||
|
private static final double HEIGHT_OFFSET = 2.8;
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final Map<UUID, TextDisplay> displays = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, ScheduledTask> tasks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public PacifistHologram(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
TextDisplay current = displays.get(uuid);
|
||||||
|
if (current != null && current.isValid() && current.getWorld().equals(player.getWorld())) {
|
||||||
|
current.teleport(positionOf(player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hide(player);
|
||||||
|
spawn(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hide(Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
ScheduledTask task = tasks.remove(uuid);
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel();
|
||||||
|
}
|
||||||
|
removeEntity(displays.remove(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAll() {
|
||||||
|
for (UUID uuid : displays.keySet().toArray(new UUID[0])) {
|
||||||
|
ScheduledTask task = tasks.remove(uuid);
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel();
|
||||||
|
}
|
||||||
|
removeEntity(displays.remove(uuid));
|
||||||
|
}
|
||||||
|
tasks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawn(Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
Location location = positionOf(player);
|
||||||
|
TextDisplay display = location.getWorld().spawn(location, TextDisplay.class, entity -> {
|
||||||
|
entity.text(Component.text("Pacifist", NamedTextColor.GOLD));
|
||||||
|
entity.setBillboard(Display.Billboard.CENTER);
|
||||||
|
entity.setSeeThrough(false);
|
||||||
|
entity.setShadowed(true);
|
||||||
|
entity.setDefaultBackground(false);
|
||||||
|
entity.setBackgroundColor(Color.fromARGB(0, 0, 0, 0));
|
||||||
|
entity.setTeleportDuration(2);
|
||||||
|
entity.setPersistent(false);
|
||||||
|
entity.addScoreboardTag(TAG);
|
||||||
|
});
|
||||||
|
displays.put(uuid, display);
|
||||||
|
|
||||||
|
ScheduledTask task = player.getScheduler().runAtFixedRate(plugin,
|
||||||
|
scheduled -> tick(player, display, scheduled), null, 1L, 2L);
|
||||||
|
if (task != null) {
|
||||||
|
tasks.put(uuid, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tick(Player player, TextDisplay display, ScheduledTask task) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
if (displays.get(uuid) != display) {
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!display.isValid() || !player.isOnline()) {
|
||||||
|
displays.remove(uuid, display);
|
||||||
|
tasks.remove(uuid);
|
||||||
|
task.cancel();
|
||||||
|
removeEntity(display);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!display.getWorld().equals(player.getWorld())) {
|
||||||
|
displays.remove(uuid, display);
|
||||||
|
tasks.remove(uuid);
|
||||||
|
task.cancel();
|
||||||
|
removeEntity(display);
|
||||||
|
spawn(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display.teleport(positionOf(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEntity(TextDisplay display) {
|
||||||
|
if (display == null || !display.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
display.getScheduler().run(plugin, scheduled -> display.remove(), null);
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
// Plugin is shutting down / scheduler unavailable; entity is non-persistent.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Location positionOf(Player player) {
|
||||||
|
return player.getLocation().add(0, HEIGHT_OFFSET, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/main/java/com/dvorakmortonxd/qoltweaks/teams/Team.java
Normal file
114
src/main/java/com/dvorakmortonxd/qoltweaks/teams/Team.java
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.dvorakmortonxd.qoltweaks.teams;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A purely cosmetic player team. Membership is tracked by UUID, the colour is
|
||||||
|
* applied to every member's no-PvP glow, and the glow can be switched off for
|
||||||
|
* the whole team.
|
||||||
|
*/
|
||||||
|
public final class Team {
|
||||||
|
|
||||||
|
private final UUID id;
|
||||||
|
private String name;
|
||||||
|
private UUID owner;
|
||||||
|
private final Set<UUID> members = new LinkedHashSet<>();
|
||||||
|
private final Set<UUID> admins = new LinkedHashSet<>();
|
||||||
|
private NamedTextColor color;
|
||||||
|
private boolean glowEnabled = true;
|
||||||
|
|
||||||
|
public Team(UUID id, String name, UUID owner) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.owner = owner;
|
||||||
|
this.members.add(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwner(UUID owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<UUID> getMembers() {
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<UUID> getAdmins() {
|
||||||
|
return admins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMember(UUID uuid) {
|
||||||
|
return members.contains(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner(UUID uuid) {
|
||||||
|
return owner != null && owner.equals(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAdmin(UUID uuid) {
|
||||||
|
return isOwner(uuid) || admins.contains(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAdmin(UUID uuid) {
|
||||||
|
admins.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAdmin(UUID uuid) {
|
||||||
|
admins.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamedTextColor getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(NamedTextColor color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGlowEnabled() {
|
||||||
|
return glowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGlowEnabled(boolean glowEnabled) {
|
||||||
|
this.glowEnabled = glowEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMember(UUID uuid) {
|
||||||
|
members.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMember(UUID uuid) {
|
||||||
|
members.remove(uuid);
|
||||||
|
admins.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return other instanceof Team team && id.equals(team.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,442 @@
|
||||||
|
package com.dvorakmortonxd.qoltweaks.teams;
|
||||||
|
|
||||||
|
import com.dvorakmortonxd.qoltweaks.nopvp.NoPvPManager;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class TeamCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private static final List<String> SUBCOMMANDS = List.of(
|
||||||
|
"create", "delete", "invite", "leave", "join", "color", "admin", "glow", "chat");
|
||||||
|
|
||||||
|
private final TeamManager manager;
|
||||||
|
private final NoPvPManager noPvPManager;
|
||||||
|
|
||||||
|
public TeamCommand(TeamManager manager, NoPvPManager noPvPManager) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.noPvPManager = noPvPManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("§cOnly players can use this command.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!player.hasPermission("qoltweaks.team")) {
|
||||||
|
player.sendMessage("§cYou don't have permission to use this command.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendUsage(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase(Locale.ROOT)) {
|
||||||
|
case "create" -> create(player, args);
|
||||||
|
case "delete" -> delete(player);
|
||||||
|
case "invite" -> invite(player, args);
|
||||||
|
case "leave" -> leave(player);
|
||||||
|
case "join" -> join(player, args);
|
||||||
|
case "color" -> color(player, args);
|
||||||
|
case "admin" -> admin(player, args);
|
||||||
|
case "glow" -> glow(player);
|
||||||
|
case "chat" -> chat(player, args);
|
||||||
|
default -> sendUsage(player);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void create(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§cUsage: /team create <name>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (manager.teamOf(player) != null) {
|
||||||
|
player.sendMessage("§cYou're already in a team. Leave or delete it first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name = args[1];
|
||||||
|
if (!TeamManager.isValidName(name)) {
|
||||||
|
player.sendMessage("§cTeam names must be 2-16 letters, numbers or underscores.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (manager.nameTaken(name)) {
|
||||||
|
player.sendMessage("§cA team called §e" + name + "§c already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
manager.create(player, name);
|
||||||
|
player.sendMessage("§aTeam §e" + name + "§a created! Invite players with §e/team invite <player>§a.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(Player player) {
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isOwner(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cOnly the team owner can delete the team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<UUID> members = new ArrayList<>(team.getMembers());
|
||||||
|
String name = team.getName();
|
||||||
|
manager.delete(team);
|
||||||
|
player.sendMessage("§cTeam §e" + name + "§c has been deleted.");
|
||||||
|
for (UUID member : members) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null) {
|
||||||
|
noPvPManager.refreshLater(online);
|
||||||
|
if (!online.equals(player)) {
|
||||||
|
online.sendMessage("§cYour team §e" + name + "§c was deleted by the owner.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invite(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§cUsage: /team invite <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isAdmin(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cOnly the team owner or an admin can invite players.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player target = Bukkit.getPlayerExact(args[1]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage("§cThat player is not online.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target.getUniqueId().equals(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cYou can't invite yourself.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (manager.teamOf(target) != null) {
|
||||||
|
player.sendMessage("§c" + target.getName() + " is already in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager.hasJoinRequest(team, target.getUniqueId())) {
|
||||||
|
manager.addMember(team, target);
|
||||||
|
noPvPManager.refreshLater(target);
|
||||||
|
player.sendMessage("§a" + target.getName() + " joined the team!");
|
||||||
|
target.sendMessage("§aYou joined team §e" + team.getName() + "§a!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.addInvite(team, target.getUniqueId());
|
||||||
|
player.sendMessage("§aInvited §e" + target.getName() + "§a to the team.");
|
||||||
|
target.sendMessage("§e" + player.getName() + "§a invited you to team §e" + team.getName()
|
||||||
|
+ "§a. Use §e/team join " + team.getName() + "§a to accept.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leave(Player player) {
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (team.isOwner(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cYou own this team. Use §e/team delete§c to disband it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<UUID> members = new ArrayList<>(team.getMembers());
|
||||||
|
String name = team.getName();
|
||||||
|
manager.removeMember(team, player);
|
||||||
|
noPvPManager.refreshLater(player);
|
||||||
|
player.sendMessage("§aYou left team §e" + name + "§a.");
|
||||||
|
for (UUID member : members) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null && !online.equals(player)) {
|
||||||
|
online.sendMessage("§e" + player.getName() + "§c left team §e" + name + "§c.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void join(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§cUsage: /team join <team>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (manager.teamOf(player) != null) {
|
||||||
|
player.sendMessage("§cYou're already in a team. Leave it first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Team team = manager.teamByName(args[1]);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cNo team called §e" + args[1] + "§c exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager.hasInvite(team, player.getUniqueId())) {
|
||||||
|
manager.addMember(team, player);
|
||||||
|
noPvPManager.refreshLater(player);
|
||||||
|
player.sendMessage("§aYou joined team §e" + team.getName() + "§a!");
|
||||||
|
notifyMembers(team, player, "§e" + player.getName() + "§a joined the team!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.addJoinRequest(team, player.getUniqueId());
|
||||||
|
player.sendMessage("§aJoin request sent to team §e" + team.getName() + "§a.");
|
||||||
|
notifyAdmins(team, "§e" + player.getName() + "§a wants to join your team. Use §e/team invite "
|
||||||
|
+ player.getName() + "§a to accept.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void color(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§cUsage: /team color <color>");
|
||||||
|
player.sendMessage("§7Colors: " + colorList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isAdmin(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cOnly the team owner or an admin can change the team colour.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String input = args[1].toLowerCase(Locale.ROOT);
|
||||||
|
if (input.equals("none") || input.equals("reset") || input.equals("clear")) {
|
||||||
|
manager.setColor(team, null);
|
||||||
|
player.sendMessage("§aTeam colour cleared.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NamedTextColor color = TeamManager.parseColor(input);
|
||||||
|
if (color == null) {
|
||||||
|
player.sendMessage("§cUnknown colour. Options: " + colorList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
manager.setColor(team, color);
|
||||||
|
player.sendMessage("§aTeam colour set to §e" + color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void admin(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§cUsage: /team admin <add|remove> <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isOwner(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cOnly the team owner can manage admins.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String action = args[1].toLowerCase(Locale.ROOT);
|
||||||
|
if (!action.equals("add") && !action.equals("remove")) {
|
||||||
|
player.sendMessage("§cUsage: /team admin <add|remove> <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length < 3) {
|
||||||
|
player.sendMessage("§cUsage: /team admin " + action + " <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player target = Bukkit.getPlayerExact(args[2]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage("§cThat player is not online.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isMember(target.getUniqueId())) {
|
||||||
|
player.sendMessage("§c" + target.getName() + " is not in your team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (team.isOwner(target.getUniqueId())) {
|
||||||
|
player.sendMessage("§cThe owner is already an admin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.equals("add")) {
|
||||||
|
manager.addAdmin(team, target.getUniqueId());
|
||||||
|
player.sendMessage("§a" + target.getName() + " is now a team admin.");
|
||||||
|
target.sendMessage("§aYou are now an admin of team §e" + team.getName() + "§a.");
|
||||||
|
} else {
|
||||||
|
manager.removeAdmin(team, target.getUniqueId());
|
||||||
|
player.sendMessage("§a" + target.getName() + " is no longer a team admin.");
|
||||||
|
target.sendMessage("§cYou are no longer an admin of team §e" + team.getName() + "§c.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void glow(Player player) {
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!team.isAdmin(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§cOnly the team owner or an admin can toggle the team glow.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean enabled = manager.toggleGlow(team);
|
||||||
|
for (UUID member : new ArrayList<>(team.getMembers())) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null) {
|
||||||
|
noPvPManager.refreshLater(online);
|
||||||
|
online.sendMessage(enabled
|
||||||
|
? "§aYour team's no-PvP glow was turned on."
|
||||||
|
: "§cYour team's no-PvP glow was turned off.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chat(Player player, String[] args) {
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
player.sendMessage("§cYou're not in a team.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length == 1) {
|
||||||
|
boolean toggled = manager.toggleTeamChat(player.getUniqueId());
|
||||||
|
player.sendMessage(toggled
|
||||||
|
? "§aTeam chat §lON§a. Your messages now go to the team only."
|
||||||
|
: "§cTeam chat §lOFF§c.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
NamedTextColor color = team.getColor() != null ? team.getColor() : NamedTextColor.AQUA;
|
||||||
|
Component message = Component.text("[", NamedTextColor.DARK_GRAY)
|
||||||
|
.append(Component.text(team.getName(), color))
|
||||||
|
.append(Component.text("] ", NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text(player.getName() + ": ", NamedTextColor.WHITE))
|
||||||
|
.append(Component.text(text, NamedTextColor.WHITE));
|
||||||
|
for (UUID member : new ArrayList<>(team.getMembers())) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null) {
|
||||||
|
online.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyMembers(Team team, Player except, String message) {
|
||||||
|
for (UUID member : new ArrayList<>(team.getMembers())) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null && !online.equals(except)) {
|
||||||
|
online.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyAdmins(Team team, String message) {
|
||||||
|
for (UUID admin : new ArrayList<>(team.getAdmins())) {
|
||||||
|
Player online = Bukkit.getPlayer(admin);
|
||||||
|
if (online != null) {
|
||||||
|
online.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Player owner = Bukkit.getPlayer(team.getOwner());
|
||||||
|
if (owner != null && !team.getAdmins().contains(team.getOwner())) {
|
||||||
|
owner.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUsage(Player player) {
|
||||||
|
player.sendMessage("§6§lTeam commands");
|
||||||
|
player.sendMessage("§e/team create <name> §7- make a team");
|
||||||
|
player.sendMessage("§e/team invite <player> §7- invite someone");
|
||||||
|
player.sendMessage("§e/team join <team> §7- request or accept an invite");
|
||||||
|
player.sendMessage("§e/team color <color> §7- set the team's glow colour");
|
||||||
|
player.sendMessage("§e/team admin <add|remove> <player> §7- manage admins");
|
||||||
|
player.sendMessage("§e/team glow §7- toggle the team glow");
|
||||||
|
player.sendMessage("§e/team chat [message] §7- team chat / toggle it");
|
||||||
|
player.sendMessage("§e/team leave §7- leave your team");
|
||||||
|
player.sendMessage("§e/team delete §7- disband your team (owner)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String colorList() {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (NamedTextColor color : TeamManager.colors()) {
|
||||||
|
names.add(color.toString());
|
||||||
|
}
|
||||||
|
return String.join("§7, §e", names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (args.length == 1) {
|
||||||
|
return filter(SUBCOMMANDS, args[0]);
|
||||||
|
}
|
||||||
|
if (args.length == 2) {
|
||||||
|
switch (args[0].toLowerCase(Locale.ROOT)) {
|
||||||
|
case "invite" -> {
|
||||||
|
return filter(onlineNames(online -> !online.equals(player) && manager.teamOf(online) == null), args[1]);
|
||||||
|
}
|
||||||
|
case "join" -> {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (Team team : manager.teams()) {
|
||||||
|
names.add(team.getName());
|
||||||
|
}
|
||||||
|
return filter(names, args[1]);
|
||||||
|
}
|
||||||
|
case "color" -> {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (NamedTextColor color : TeamManager.colors()) {
|
||||||
|
names.add(color.toString());
|
||||||
|
}
|
||||||
|
names.add("none");
|
||||||
|
return filter(names, args[1]);
|
||||||
|
}
|
||||||
|
case "admin" -> {
|
||||||
|
return filter(List.of("add", "remove"), args[1]);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (args.length == 3 && args[0].equalsIgnoreCase("admin")) {
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return filter(onlineNames(online -> team.isMember(online.getUniqueId())), args[2]);
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> onlineNames(java.util.function.Predicate<Player> predicate) {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (Player online : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (predicate.test(online)) {
|
||||||
|
names.add(online.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> filter(List<String> options, String prefix) {
|
||||||
|
String lower = prefix.toLowerCase(Locale.ROOT);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (String option : options) {
|
||||||
|
if (option.toLowerCase(Locale.ROOT).startsWith(lower)) {
|
||||||
|
result.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.dvorakmortonxd.qoltweaks.teams;
|
||||||
|
|
||||||
|
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||||
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class TeamListener implements Listener {
|
||||||
|
|
||||||
|
private final TeamManager manager;
|
||||||
|
|
||||||
|
public TeamListener(TeamManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs at NORMAL so the scoreboard team (and thus glow colour) is applied
|
||||||
|
* before {@code NoPvPListener} turns the glow on at MONITOR.
|
||||||
|
*/
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL)
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
manager.syncPlayer(player);
|
||||||
|
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team != null && team.isAdmin(player.getUniqueId())) {
|
||||||
|
List<String> pending = manager.pendingRequestNames(team);
|
||||||
|
if (!pending.isEmpty()) {
|
||||||
|
player.sendMessage("§ePlayers wanting to join your team: §a" + String.join("§7, §a", pending)
|
||||||
|
+ "§e. Use §a/team invite <player>§e to accept.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Redirects chat into the team channel when a player has team chat toggled on. */
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
|
public void onChat(AsyncChatEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!manager.isTeamChat(player.getUniqueId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Team team = manager.teamOf(player);
|
||||||
|
if (team == null) {
|
||||||
|
manager.clearTeamChat(player.getUniqueId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Audience> viewers = event.viewers();
|
||||||
|
viewers.clear();
|
||||||
|
for (UUID member : team.getMembers()) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null) {
|
||||||
|
viewers.add(online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NamedTextColor color = team.getColor() != null ? team.getColor() : NamedTextColor.AQUA;
|
||||||
|
event.renderer((source, sourceDisplayName, message, viewer) ->
|
||||||
|
Component.text("[", NamedTextColor.DARK_GRAY)
|
||||||
|
.append(Component.text(team.getName(), color))
|
||||||
|
.append(Component.text("] ", NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text(source.getName() + ": ", NamedTextColor.WHITE))
|
||||||
|
.append(message.colorIfAbsent(NamedTextColor.WHITE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,418 @@
|
||||||
|
package com.dvorakmortonxd.qoltweaks.teams;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores cosmetic teams, their members and colours, and mirrors them onto the
|
||||||
|
* main scoreboard so the no-PvP glow picks up the team colour.
|
||||||
|
*/
|
||||||
|
public final class TeamManager {
|
||||||
|
|
||||||
|
private static final List<NamedTextColor> COLORS = List.of(
|
||||||
|
NamedTextColor.BLACK, NamedTextColor.DARK_BLUE, NamedTextColor.DARK_GREEN,
|
||||||
|
NamedTextColor.DARK_AQUA, NamedTextColor.DARK_RED, NamedTextColor.DARK_PURPLE,
|
||||||
|
NamedTextColor.GOLD, NamedTextColor.GRAY, NamedTextColor.DARK_GRAY,
|
||||||
|
NamedTextColor.BLUE, NamedTextColor.GREEN, NamedTextColor.AQUA,
|
||||||
|
NamedTextColor.RED, NamedTextColor.LIGHT_PURPLE, NamedTextColor.YELLOW,
|
||||||
|
NamedTextColor.WHITE);
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final File dataFile;
|
||||||
|
private final Scoreboard scoreboard;
|
||||||
|
|
||||||
|
private final Map<UUID, Team> teams = new LinkedHashMap<>(); // team id -> team
|
||||||
|
private final Map<UUID, UUID> teamByMember = new LinkedHashMap<>(); // member -> team id
|
||||||
|
private final Map<String, UUID> teamByName = new LinkedHashMap<>(); // lowercase name -> team id
|
||||||
|
private final Map<UUID, UUID> joinRequests = new ConcurrentHashMap<>(); // player -> team id
|
||||||
|
private final Map<UUID, Set<UUID>> invites = new ConcurrentHashMap<>(); // team id -> invited players
|
||||||
|
private final Set<UUID> teamChat = ConcurrentHashMap.newKeySet(); // players with team chat toggled on
|
||||||
|
|
||||||
|
public TeamManager(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.dataFile = new File(plugin.getDataFolder(), "teams.yml");
|
||||||
|
this.scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
|
||||||
|
synchronized (this) {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
refreshAllScoreboards();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidName(String name) {
|
||||||
|
return name != null && name.matches("[A-Za-z0-9_]{2,16}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<NamedTextColor> colors() {
|
||||||
|
return COLORS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NamedTextColor parseColor(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = input.toLowerCase(Locale.ROOT).replace(" ", "").replace("-", "");
|
||||||
|
for (NamedTextColor color : COLORS) {
|
||||||
|
String name = color.toString();
|
||||||
|
if (name.equals(normalized) || name.replace("_", "").equals(normalized)) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Team teamOf(Player player) {
|
||||||
|
UUID teamId = teamByMember.get(player.getUniqueId());
|
||||||
|
return teamId == null ? null : teams.get(teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Team teamByName(String name) {
|
||||||
|
UUID teamId = teamByName.get(name.toLowerCase(Locale.ROOT));
|
||||||
|
return teamId == null ? null : teams.get(teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean nameTaken(String name) {
|
||||||
|
return teamByName.containsKey(name.toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Collection<Team> teams() {
|
||||||
|
return new ArrayList<>(teams.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Team create(Player owner, String name) {
|
||||||
|
Team team;
|
||||||
|
synchronized (this) {
|
||||||
|
team = new Team(UUID.randomUUID(), name, owner.getUniqueId());
|
||||||
|
index(team);
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
refreshScoreboard(team.getId());
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Team team) {
|
||||||
|
synchronized (this) {
|
||||||
|
teams.remove(team.getId());
|
||||||
|
teamByName.remove(team.getName().toLowerCase(Locale.ROOT));
|
||||||
|
for (UUID member : team.getMembers()) {
|
||||||
|
teamByMember.remove(member);
|
||||||
|
joinRequests.remove(member);
|
||||||
|
teamChat.remove(member);
|
||||||
|
}
|
||||||
|
invites.remove(team.getId());
|
||||||
|
joinRequests.entrySet().removeIf(entry -> entry.getValue().equals(team.getId()));
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
plugin.getServer().getGlobalRegionScheduler().run(plugin, task -> {
|
||||||
|
org.bukkit.scoreboard.Team board = scoreboard.getTeam(scoreboardTeamName(team.getId()));
|
||||||
|
if (board != null) {
|
||||||
|
board.unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMember(Team team, Player player) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (teams.get(team.getId()) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
team.addMember(player.getUniqueId());
|
||||||
|
teamByMember.put(player.getUniqueId(), team.getId());
|
||||||
|
joinRequests.remove(player.getUniqueId());
|
||||||
|
invites.values().forEach(pending -> pending.remove(player.getUniqueId()));
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
refreshScoreboard(team.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMember(Team team, Player player) {
|
||||||
|
synchronized (this) {
|
||||||
|
team.removeMember(player.getUniqueId());
|
||||||
|
teamByMember.remove(player.getUniqueId());
|
||||||
|
teamChat.remove(player.getUniqueId());
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
refreshScoreboard(team.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(Team team, NamedTextColor color) {
|
||||||
|
synchronized (this) {
|
||||||
|
team.setColor(color);
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
refreshScoreboard(team.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAdmin(Team team, UUID player) {
|
||||||
|
synchronized (this) {
|
||||||
|
team.addAdmin(player);
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAdmin(Team team, UUID player) {
|
||||||
|
synchronized (this) {
|
||||||
|
team.removeAdmin(player);
|
||||||
|
scheduleSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean toggleGlow(Team team) {
|
||||||
|
synchronized (this) {
|
||||||
|
boolean enabled = !team.isGlowEnabled();
|
||||||
|
team.setGlowEnabled(enabled);
|
||||||
|
scheduleSave();
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean shouldGlow(Player player) {
|
||||||
|
UUID teamId = teamByMember.get(player.getUniqueId());
|
||||||
|
if (teamId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Team team = teams.get(teamId);
|
||||||
|
return team != null && team.isGlowEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTeamChat(UUID player) {
|
||||||
|
return teamChat.contains(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggles team chat mode and returns the new state. */
|
||||||
|
public boolean toggleTeamChat(UUID player) {
|
||||||
|
if (teamChat.remove(player)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
teamChat.add(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTeamChat(UUID player) {
|
||||||
|
teamChat.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInvite(Team team, UUID player) {
|
||||||
|
invites.computeIfAbsent(team.getId(), id -> ConcurrentHashMap.newKeySet()).add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasInvite(Team team, UUID player) {
|
||||||
|
Set<UUID> pending = invites.get(team.getId());
|
||||||
|
return pending != null && pending.contains(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addJoinRequest(Team team, UUID player) {
|
||||||
|
joinRequests.put(player, team.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasJoinRequest(Team team, UUID player) {
|
||||||
|
return team.getId().equals(joinRequests.get(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> pendingRequestNames(Team team) {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (Map.Entry<UUID, UUID> entry : joinRequests.entrySet()) {
|
||||||
|
if (entry.getValue().equals(team.getId())) {
|
||||||
|
Player online = Bukkit.getPlayer(entry.getKey());
|
||||||
|
if (online != null) {
|
||||||
|
names.add(online.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a joining player's name to their team's scoreboard team (also picks up renames). */
|
||||||
|
public void syncPlayer(Player player) {
|
||||||
|
UUID teamId;
|
||||||
|
synchronized (this) {
|
||||||
|
teamId = teamByMember.get(player.getUniqueId());
|
||||||
|
}
|
||||||
|
if (teamId != null) {
|
||||||
|
refreshScoreboard(teamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshAllScoreboards() {
|
||||||
|
List<UUID> ids;
|
||||||
|
synchronized (this) {
|
||||||
|
ids = new ArrayList<>(teams.keySet());
|
||||||
|
}
|
||||||
|
plugin.getServer().getGlobalRegionScheduler().run(plugin, task -> {
|
||||||
|
for (UUID id : ids) {
|
||||||
|
refreshScoreboardNow(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void index(Team team) {
|
||||||
|
teams.put(team.getId(), team);
|
||||||
|
teamByName.put(team.getName().toLowerCase(Locale.ROOT), team.getId());
|
||||||
|
for (UUID member : team.getMembers()) {
|
||||||
|
teamByMember.put(member, team.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshScoreboard(UUID teamId) {
|
||||||
|
plugin.getServer().getGlobalRegionScheduler().run(plugin, task -> refreshScoreboardNow(teamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshScoreboardNow(UUID teamId) {
|
||||||
|
NamedTextColor color;
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
synchronized (this) {
|
||||||
|
Team team = teams.get(teamId);
|
||||||
|
if (team == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
color = team.getColor();
|
||||||
|
for (UUID member : team.getMembers()) {
|
||||||
|
Player online = Bukkit.getPlayer(member);
|
||||||
|
if (online != null) {
|
||||||
|
names.add(online.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String boardName = scoreboardTeamName(teamId);
|
||||||
|
org.bukkit.scoreboard.Team board = scoreboard.getTeam(boardName);
|
||||||
|
if (board == null) {
|
||||||
|
board = scoreboard.registerNewTeam(boardName);
|
||||||
|
}
|
||||||
|
board.setAllowFriendlyFire(true);
|
||||||
|
board.setCanSeeFriendlyInvisibles(true);
|
||||||
|
board.color(color);
|
||||||
|
|
||||||
|
Set<String> wanted = Set.copyOf(names);
|
||||||
|
for (String entry : new ArrayList<>(board.getEntries())) {
|
||||||
|
if (!wanted.contains(entry)) {
|
||||||
|
board.removeEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String name : names) {
|
||||||
|
if (!board.hasEntry(name)) {
|
||||||
|
board.addEntry(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String scoreboardTeamName(UUID teamId) {
|
||||||
|
return "qt_" + teamId.toString().replace("-", "").substring(0, 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
if (!dataFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile);
|
||||||
|
ConfigurationSection root = config.getConfigurationSection("teams");
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String key : root.getKeys(false)) {
|
||||||
|
ConfigurationSection section = root.getConfigurationSection(key);
|
||||||
|
if (section == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
UUID id = parseUuid(key);
|
||||||
|
UUID owner = parseUuid(section.getString("owner"));
|
||||||
|
String name = section.getString("name");
|
||||||
|
if (id == null || owner == null || name == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Team team = new Team(id, name, owner);
|
||||||
|
for (String member : section.getStringList("members")) {
|
||||||
|
UUID uuid = parseUuid(member);
|
||||||
|
if (uuid != null) {
|
||||||
|
team.addMember(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
team.addMember(owner);
|
||||||
|
for (String admin : section.getStringList("admins")) {
|
||||||
|
UUID uuid = parseUuid(admin);
|
||||||
|
if (uuid != null) {
|
||||||
|
team.addAdmin(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
team.setGlowEnabled(section.getBoolean("glow", true));
|
||||||
|
NamedTextColor color = parseColor(section.getString("color"));
|
||||||
|
if (color != null) {
|
||||||
|
team.setColor(color);
|
||||||
|
}
|
||||||
|
index(team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleSave() {
|
||||||
|
YamlConfiguration config = buildConfig();
|
||||||
|
plugin.getServer().getGlobalRegionScheduler().run(plugin, task -> writeConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveNow() {
|
||||||
|
writeConfig(buildConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
private YamlConfiguration buildConfig() {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
ConfigurationSection root = config.createSection("teams");
|
||||||
|
for (Team team : teams.values()) {
|
||||||
|
String path = team.getId().toString();
|
||||||
|
root.set(path + ".name", team.getName());
|
||||||
|
root.set(path + ".owner", team.getOwner().toString());
|
||||||
|
if (team.getColor() != null) {
|
||||||
|
root.set(path + ".color", team.getColor().toString());
|
||||||
|
}
|
||||||
|
List<String> members = new ArrayList<>();
|
||||||
|
for (UUID member : team.getMembers()) {
|
||||||
|
members.add(member.toString());
|
||||||
|
}
|
||||||
|
root.set(path + ".members", members);
|
||||||
|
List<String> admins = new ArrayList<>();
|
||||||
|
for (UUID admin : team.getAdmins()) {
|
||||||
|
admins.add(admin.toString());
|
||||||
|
}
|
||||||
|
root.set(path + ".admins", admins);
|
||||||
|
root.set(path + ".glow", team.isGlowEnabled());
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeConfig(YamlConfiguration config) {
|
||||||
|
if (!dataFile.getParentFile().exists() && !dataFile.getParentFile().mkdirs()) {
|
||||||
|
plugin.getLogger().severe("Could not create data folder for teams.yml");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
config.save(dataFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Could not save teams.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID parseUuid(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return UUID.fromString(value);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,8 +14,14 @@ commands:
|
||||||
nopvp:
|
nopvp:
|
||||||
description: Toggle no-PvP mode so other players cannot damage you and you cannot damage them.
|
description: Toggle no-PvP mode so other players cannot damage you and you cannot damage them.
|
||||||
usage: /nopvp
|
usage: /nopvp
|
||||||
|
team:
|
||||||
|
description: Create and manage a cosmetic team whose colour is used for the no-PvP glow.
|
||||||
|
usage: /team <create|delete|invite|leave|join|color>
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
qoltweaks.nopvp:
|
qoltweaks.nopvp:
|
||||||
description: Allows use of /nopvp
|
description: Allows use of /nopvp
|
||||||
default: true
|
default: true
|
||||||
|
qoltweaks.team:
|
||||||
|
description: Allows use of /team
|
||||||
|
default: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue