From 34e873ed1760d919313c35a8dfe92c4d4a8c738f Mon Sep 17 00:00:00 2001
From: yaohunya <1763917516@qq.com>
Date: Sat, 2 Aug 2025 09:44:57 +0800
Subject: [PATCH] 1.0.0
---
pom.xml | 47 ++++
.../java/com/yaohun/tnttag/PapiExpansion.java | 90 +++++++
src/main/java/com/yaohun/tnttag/TagMain.java | 74 ++++++
.../java/com/yaohun/tnttag/config/Config.java | 20 ++
.../yaohun/tnttag/counter/LobbyCounter.java | 108 ++++++++
.../yaohun/tnttag/counter/RestartCounter.java | 55 +++++
.../tnttag/listener/DoubleJumpListener.java | 96 ++++++++
.../yaohun/tnttag/listener/GameListener.java | 58 +++++
.../tnttag/listener/PlayerListener.java | 195 +++++++++++++++
.../tnttag/listener/ProtectListener.java | 101 ++++++++
.../com/yaohun/tnttag/manage/GameManager.java | 136 +++++++++++
.../yaohun/tnttag/manage/RoundManager.java | 231 ++++++++++++++++++
.../com/yaohun/tnttag/task/CountdownTask.java | 4 +
.../tnttag/task/TntSmokeEffectTask.java | 32 +++
.../com/yaohun/tnttag/util/GameState.java | 7 +
.../com/yaohun/tnttag/util/MessageUtil.java | 61 +++++
.../com/yaohun/tnttag/util/PlayerState.java | 6 +
.../java/com/yaohun/tnttag/util/SideBar.java | 51 ++++
.../tnttag/util/SpeedEffectApplier.java | 34 +++
.../com/yaohun/tnttag/util/StackUtil.java | 41 ++++
src/main/resources/config.yml | 2 +
src/main/resources/message.yml | 11 +
src/main/resources/plugin.yml | 9 +
23 files changed, 1469 insertions(+)
create mode 100644 pom.xml
create mode 100644 src/main/java/com/yaohun/tnttag/PapiExpansion.java
create mode 100644 src/main/java/com/yaohun/tnttag/TagMain.java
create mode 100644 src/main/java/com/yaohun/tnttag/config/Config.java
create mode 100644 src/main/java/com/yaohun/tnttag/counter/LobbyCounter.java
create mode 100644 src/main/java/com/yaohun/tnttag/counter/RestartCounter.java
create mode 100644 src/main/java/com/yaohun/tnttag/listener/DoubleJumpListener.java
create mode 100644 src/main/java/com/yaohun/tnttag/listener/GameListener.java
create mode 100644 src/main/java/com/yaohun/tnttag/listener/PlayerListener.java
create mode 100644 src/main/java/com/yaohun/tnttag/listener/ProtectListener.java
create mode 100644 src/main/java/com/yaohun/tnttag/manage/GameManager.java
create mode 100644 src/main/java/com/yaohun/tnttag/manage/RoundManager.java
create mode 100644 src/main/java/com/yaohun/tnttag/task/CountdownTask.java
create mode 100644 src/main/java/com/yaohun/tnttag/task/TntSmokeEffectTask.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/GameState.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/MessageUtil.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/PlayerState.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/SideBar.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/SpeedEffectApplier.java
create mode 100644 src/main/java/com/yaohun/tnttag/util/StackUtil.java
create mode 100644 src/main/resources/config.yml
create mode 100644 src/main/resources/message.yml
create mode 100644 src/main/resources/plugin.yml
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0807e96
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ com.yaohun.main
+ AuTntTag
+ 1.0-SNAPSHOT
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ public-rpg
+ https://repo.aurora-pixels.com/repository/public-rpg/
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.12.2
+
+
+ me.Demon.DemonPlugin
+ DemonAPI
+ 2.2.9
+
+
+ me.Demon.DemonWarps
+ DemonWarps
+ 2.0.3
+
+
+ me.clip.placeholderapi
+ PlaceholderAPI
+ 2.9.2
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/tnttag/PapiExpansion.java b/src/main/java/com/yaohun/tnttag/PapiExpansion.java
new file mode 100644
index 0000000..b958e20
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/PapiExpansion.java
@@ -0,0 +1,90 @@
+package com.yaohun.tnttag;
+
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.manage.RoundManager;
+import com.yaohun.tnttag.util.GameState;
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+
+/**
+ * @Author: Baka
+ * @Date: 2019/12/23 13:20
+ */
+public class PapiExpansion extends PlaceholderExpansion {
+ private Plugin plugin;
+
+ public PapiExpansion(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @Override
+ public boolean canRegister() {
+ return true;
+ }
+
+ @Override
+ public String getAuthor() {
+ return plugin.getDescription().getAuthors().toString();
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "game";
+ }
+
+ @Override
+ public String getVersion() {
+ return plugin.getDescription().getVersion();
+ }
+
+ @Override
+ public String onPlaceholderRequest(Player player, String identifier) {
+ GameManager game = TagMain.getGame();
+ if ("stats".equalsIgnoreCase(identifier)) {
+ if(game.gameState == GameState.LOBBY){
+ return "";
+ }
+ if(game.getAlivePlayers().isEmpty()){
+ return "";
+ }
+ RoundManager roundManager = game.getRoundManager();
+ if(game.getAlivePlayers().contains(player)){
+ if (roundManager.isTnt(player)) {
+ return "§c§l炸弹客";
+ } else {
+ return "§a§l存活";
+ }
+ } else {
+ return "";
+ }
+ }else if ("tabstats".equalsIgnoreCase(identifier)) {
+ if(game.gameState == GameState.LOBBY){
+ if(player.isOp()) {
+ return "§7[§c管理员§7]§4";
+ } else {
+ return "§7[§2玩家§7]§r";
+ }
+ }
+ if(game.getAlivePlayers().isEmpty()){
+ return "";
+ }
+ RoundManager roundManager = game.getRoundManager();
+ if(game.getAlivePlayers().contains(player)){
+ if (roundManager.isTnt(player)) {
+ return "§7[§d§l炸弹客§7]§5";
+ } else {
+ return "§7[§a§l存活§7]§r";
+ }
+ } else {
+ return "§7[§c§l阵亡§7]§7";
+ }
+ }
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/tnttag/TagMain.java b/src/main/java/com/yaohun/tnttag/TagMain.java
new file mode 100644
index 0000000..993f4d3
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/TagMain.java
@@ -0,0 +1,74 @@
+package com.yaohun.tnttag;
+
+import com.yaohun.tnttag.config.Config;
+import com.yaohun.tnttag.counter.LobbyCounter;
+import com.yaohun.tnttag.listener.DoubleJumpListener;
+import com.yaohun.tnttag.listener.GameListener;
+import com.yaohun.tnttag.listener.PlayerListener;
+import com.yaohun.tnttag.listener.ProtectListener;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.util.MessageUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class TagMain extends JavaPlugin {
+
+ private static TagMain instance;
+ private static GameManager gameManager;
+
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ saveDefaultConfig();
+ Config.reloadConfig(this);
+ MessageUtil.init(this);
+ gameManager = new GameManager(this);
+ getServer().getPluginManager().registerEvents(new GameListener(),this);
+ getServer().getPluginManager().registerEvents(new PlayerListener(),this);
+ getServer().getPluginManager().registerEvents(new ProtectListener(),this);
+ getServer().getPluginManager().registerEvents(new DoubleJumpListener(gameManager),this);
+
+ Bukkit.getConsoleSender().sendMessage("§b[斗魂帝国] §7趣味小游戏: §6烫手山芋(炸弹版)");
+ Bukkit.getConsoleSender().sendMessage("§b[斗魂帝国] §7插件作者: 妖魂吖");
+ if(getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){
+ new PapiExpansion(this).register();
+ }
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ LobbyCounter.startWaiterMessage();
+ }
+ }.runTaskLater(this,20 * 10);
+ }
+
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if("forcestart".equalsIgnoreCase(label) && sender.isOp()){
+ LobbyCounter.forceStartGame();
+ return true;
+ }
+ if("game".equalsIgnoreCase(label) && sender.isOp()){
+ if(args.length == 1 && "reload".equalsIgnoreCase(args[0])){
+ MessageUtil.init(this);
+ Config.reloadConfig(this);
+ sender.sendMessage("[烫手山芋] 配置/语言文件已重载.");
+ }
+ return true;
+ }
+ return false;
+ }
+
+
+ public static TagMain inst() {
+ return instance;
+ }
+
+ public static GameManager getGame() {
+ return gameManager;
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/config/Config.java b/src/main/java/com/yaohun/tnttag/config/Config.java
new file mode 100644
index 0000000..d19f8d8
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/config/Config.java
@@ -0,0 +1,20 @@
+package com.yaohun.tnttag.config;
+
+import com.yaohun.tnttag.TagMain;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class Config {
+
+ public static String backServer;
+ public static int requirementPlayer;
+ public static void reloadConfig(TagMain plugin){
+ plugin.reloadConfig();
+ plugin.saveConfig();
+ FileConfiguration config = plugin.getConfig();
+ requirementPlayer = config.getInt("requirementPlayer", 30);
+ Bukkit.getConsoleSender().sendMessage("[设置] 最低所需玩家: "+requirementPlayer);
+ backServer = config.getString("backServer","loginlobby01");
+ Bukkit.getConsoleSender().sendMessage("[设置] BungeeCord大厅: "+backServer);
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/counter/LobbyCounter.java b/src/main/java/com/yaohun/tnttag/counter/LobbyCounter.java
new file mode 100644
index 0000000..5f84695
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/counter/LobbyCounter.java
@@ -0,0 +1,108 @@
+package com.yaohun.tnttag.counter;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.config.Config;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.task.TntSmokeEffectTask;
+import com.yaohun.tnttag.util.GameState;
+import com.yaohun.tnttag.util.MessageUtil;
+import com.yaohun.tnttag.util.SpeedEffectApplier;
+import me.Demon.DemonPlugin.Util.RandomUtil;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Sound;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.entity.Player;
+import org.bukkit.potion.PotionEffect;
+
+import static sun.audio.AudioPlayer.player;
+
+public class LobbyCounter {
+
+ // 每多少秒提示一次还差多少人就可以开始游戏
+ private static final int WAITER_TIME_DEFAULT = 60;
+ private static int waiterTime = 60;
+ private static int waiterTask;
+
+ public static void startWaiterMessage(){
+ startLobby();
+ waiterTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(TagMain.inst(), () -> {
+ waiterTime--;
+ if(waiterTime <= 0){
+ waiterTime = WAITER_TIME_DEFAULT;
+ int online = Bukkit.getOnlinePlayers().size();
+ int requirement = Config.requirementPlayer;
+ if(online < requirement){
+ int value = requirement - online;
+ String message = MessageUtil.getMessage("insufficient_number_of_people").replace("{value}",String.valueOf(value));
+ Bukkit.broadcastMessage(message);
+ MessageUtil.playSound(Sound.BLOCK_COMPARATOR_CLICK);
+ }
+ }
+ },0L,20L);
+ }
+
+ private static final int LOBBY_TIME_DEFAULT = 181;
+ private static int lobbyTime = 181;
+ private static int lobbyTask;
+ private static void startLobby(){
+ String message = MessageUtil.getMessage("start_countdown");
+ lobbyTask = Bukkit.getScheduler().scheduleSyncRepeatingTask(TagMain.inst(), () -> {
+ lobbyTime--;
+ for (Player player : Bukkit.getOnlinePlayers()){
+ player.setLevel(lobbyTime);
+ }
+ // 当玩家人数小于需求人数时,倒计时时间改为 LOBBY_TIME_DEFAULT 秒
+ int online = Bukkit.getOnlinePlayers().size();
+ if(online < Config.requirementPlayer){
+ lobbyTime = LOBBY_TIME_DEFAULT;
+ }
+ switch (lobbyTime){
+ case 60:
+ Bukkit.broadcastMessage(message.replace("%seconds%",String.valueOf(lobbyTime)));
+ break;
+ case 45:
+ case 30:
+ case 15:
+ case 10:
+ case 5:
+ case 4:
+ case 3:
+ case 2:
+ case 1:
+ Bukkit.broadcastMessage(message.replace("%seconds%",String.valueOf(lobbyTime)));
+ MessageUtil.playSound(Sound.BLOCK_COMPARATOR_CLICK);
+ break;
+ case 0:
+ forceStartGame();
+
+ }
+ },0L,20L);
+ }
+
+ public static void forceStartGame(){
+ // 关闭计时器
+ Bukkit.getScheduler().cancelTask(waiterTask);
+ Bukkit.getScheduler().cancelTask(lobbyTask);
+ // 启动游戏状态
+ GameManager manager = TagMain.getGame();
+ manager.setGameState(GameState.INGAME);
+ // 执行玩家开始
+ for (Player all : Bukkit.getOnlinePlayers()){
+ all.setAllowFlight(false);
+ all.setFlying(false);
+ manager.addPlayer(all);
+ all.teleport(WarpAPI.getWarpLocation("复活点"+ RandomUtil.getRandomInt(1,5)));
+ all.getInventory().clear();
+ MessageUtil.playSound(Sound.ENTITY_ENDERDRAGON_GROWL);
+ for (PotionEffect effect : all.getActivePotionEffects()) {
+ all.removePotionEffect(effect.getType());
+ }
+ SpeedEffectApplier.applyInfiniteSpeed(all, 1);
+ }
+ TagMain.getGame().startGame();
+ // 每 4 tick 检查一次(0.2秒)
+ new TntSmokeEffectTask(manager).runTaskTimer(TagMain.inst(), 0L, 4L);
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/counter/RestartCounter.java b/src/main/java/com/yaohun/tnttag/counter/RestartCounter.java
new file mode 100644
index 0000000..eb0dd3c
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/counter/RestartCounter.java
@@ -0,0 +1,55 @@
+package com.yaohun.tnttag.counter;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.config.Config;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.util.GameState;
+import com.yaohun.tnttag.util.MessageUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Random;
+
+public class RestartCounter {
+
+ private static int restarTime = 30;
+
+ public static void restart() {
+ GameManager gameManager = TagMain.getGame();
+ gameManager.setGameState(GameState.RESTART);
+ Bukkit.getScheduler().cancelAllTasks();
+ Bukkit.broadcastMessage("§f[§c系统§f] §a游戏即将结束,你将在 §6"+restarTime+"秒 §a后被传送到大厅!");
+ Bukkit.getScheduler().scheduleSyncRepeatingTask(TagMain.inst(), () -> {
+ --restarTime;
+ switch (restarTime) {
+ case 0:
+ Bukkit.shutdown();
+ break;
+ case 1:
+ Bukkit.broadcastMessage("§f[§c系统§f] §a游戏即将结束,你将在 §6"+restarTime+"秒 §a后被传送到大厅!");
+ MessageUtil.playSound(Sound.BLOCK_COMPARATOR_CLICK);
+ String lobbyName = Config.backServer;
+ for (Player all : Bukkit.getOnlinePlayers()) {
+ DemonAPI.sendPlayerToBCServer(all, lobbyName);
+ }
+ return;
+ case 15:
+ case 10:
+ case 5:
+ case 4:
+ case 3:
+ case 2:
+ Bukkit.broadcastMessage("§f[§c系统§f] §a游戏即将结束,你将在 §6"+restarTime+"秒 §a后被传送到大厅!");
+ MessageUtil.playSound(Sound.BLOCK_COMPARATOR_CLICK);
+ }
+ }, 0L, 20L);
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/listener/DoubleJumpListener.java b/src/main/java/com/yaohun/tnttag/listener/DoubleJumpListener.java
new file mode 100644
index 0000000..465dfac
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/listener/DoubleJumpListener.java
@@ -0,0 +1,96 @@
+package com.yaohun.tnttag.listener;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.util.GameState;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Particle;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerToggleFlightEvent;
+import org.bukkit.util.Vector;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class DoubleJumpListener implements Listener {
+ private final GameManager gameManager;
+ private final Set usedDoubleJump = new HashSet<>();
+
+ public DoubleJumpListener(GameManager gameManager) {
+ this.gameManager = gameManager;
+ }
+
+ @EventHandler
+ public void onPlayerToggleFlight(PlayerToggleFlightEvent event) {
+ Player player = event.getPlayer();
+
+ // 仅允许在游戏中,并且是 TNT 玩家使用二段跳
+ if (gameManager.gameState != GameState.INGAME) {
+ return;
+ }
+ if (!gameManager.getRoundManager().isTnt(player)) {
+ return;
+ }
+ // 防止重复使用
+ if (usedDoubleJump.contains(player.getUniqueId())) {
+ return;
+ }
+ // 防止原版飞行
+ event.setCancelled(true);
+ player.setAllowFlight(false);
+ player.setFlying(false);
+
+ // 推动玩家向前斜上方跳跃
+ Vector direction = player.getLocation().getDirection().multiply(1.6).setY(0.8);
+ player.setVelocity(direction);
+
+ player.getWorld().playSound(player.getLocation(), Sound.ENTITY_BAT_TAKEOFF, 1f, 1f);
+ player.getWorld().spawnParticle(Particle.CLOUD, player.getLocation(), 10);
+
+ // 加入冷却队列
+ usedDoubleJump.add(player.getUniqueId());
+
+ // 3秒后允许再次使用
+ Bukkit.getScheduler().runTaskLater(TagMain.inst(), () -> {
+ if (gameManager.getRoundManager().isTnt(player)) {
+ player.setAllowFlight(true);
+ }
+ usedDoubleJump.remove(player.getUniqueId());
+ }, 60L);
+ }
+
+ /**
+ * 当玩家落地时,重新允许飞行(准备下次跳)
+ */
+ @EventHandler
+ public void onMove(PlayerMoveEvent event) {
+ Player player = event.getPlayer();
+
+ if (gameManager.gameState != GameState.INGAME) {
+ return;
+ }
+ if (!gameManager.getRoundManager().isTnt(player)) {
+ return;
+ }
+ if (player.isOnGround() && !usedDoubleJump.contains(player.getUniqueId())) {
+ // 启用飞行作为二段跳触发器
+ player.setAllowFlight(true);
+ }
+ }
+
+ /**
+ * 玩家离开游戏时移除状态
+ */
+ @EventHandler
+ public void onQuit(PlayerQuitEvent event) {
+ Player player = event.getPlayer();
+ usedDoubleJump.remove(player.getUniqueId());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/tnttag/listener/GameListener.java b/src/main/java/com/yaohun/tnttag/listener/GameListener.java
new file mode 100644
index 0000000..01708f8
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/listener/GameListener.java
@@ -0,0 +1,58 @@
+package com.yaohun.tnttag.listener;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.manage.RoundManager;
+import com.yaohun.tnttag.util.GameState;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.Particle;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+
+public class GameListener implements Listener {
+
+ @EventHandler
+ public void onTntHit(EntityDamageByEntityEvent e){
+ if(e.getDamager() instanceof Player){
+ if(e.getEntity() instanceof Player){
+ GameManager gameManager = TagMain.getGame();
+ if(gameManager.getGameState() != GameState.INGAME) {
+ return;
+ }
+ // 获取 RoundManager
+ RoundManager roundManager = gameManager.getRoundManager();
+ Player attacker = (Player) e.getDamager();
+ if(!gameManager.isAlive(attacker)){
+ e.setCancelled(true);
+ attacker.teleport(WarpAPI.getWarpLocation("游戏场地"));
+ attacker.playSound(attacker.getLocation(), Sound.BLOCK_COMPARATOR_CLICK,0.8f,1.2f);
+ return;
+ }
+ // 只有 TNT 持有者可以传递
+ if (!roundManager.isTnt(attacker)) {
+ return;
+ }
+ Player victim = (Player) e.getEntity();
+ if(!gameManager.isAlive(victim)){
+ return;
+ }
+ // 不能把 TNT 传给另一个已经持有 TNT 的人
+ if (roundManager.isTnt(victim)) {
+ return;
+ }
+ // 执行传递
+ roundManager.passTnt(attacker, victim);
+ e.setDamage(0.0D);
+
+ victim.sendTitle("§c你被传染了!", "§7快把 TNT 传出去!", 10, 40, 10);
+ attacker.sendTitle("§a传递成功", "", 10, 20, 10);
+ victim.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, victim.getLocation(), 10);
+ victim.getWorld().playSound(victim.getLocation(), Sound.ENTITY_TNT_PRIMED, 1F, 1F);
+
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/listener/PlayerListener.java b/src/main/java/com/yaohun/tnttag/listener/PlayerListener.java
new file mode 100644
index 0000000..c12ab2d
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/listener/PlayerListener.java
@@ -0,0 +1,195 @@
+package com.yaohun.tnttag.listener;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.config.Config;
+import com.yaohun.tnttag.counter.LobbyCounter;
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.util.GameState;
+import com.yaohun.tnttag.util.MessageUtil;
+import com.yaohun.tnttag.util.SideBar;
+import com.yaohun.tnttag.util.StackUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.*;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.omg.CORBA.INV_FLAG;
+
+public class PlayerListener implements Listener {
+
+ @EventHandler
+ public void onFoodLevelChange(FoodLevelChangeEvent event){
+ event.setCancelled(true);
+ }
+
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent e){
+ Player player = e.getPlayer();
+ String playerName = player.getName();
+ e.setJoinMessage(null);
+ for (PotionEffect effetcs : player.getActivePotionEffects()) {
+ player.removePotionEffect(effetcs.getType());
+ }
+ GameManager game = TagMain.getGame();
+ if(game.gameState != GameState.LOBBY){
+ return;
+ }
+ player.sendTitle("§6斗魂帝国","§3趣味小游戏 §f- §c烫手山芋§b(炸弹版)",10,60,20);
+ String message = MessageUtil.getMessage("join_message").replace("{online}",String.valueOf(Bukkit.getOnlinePlayers().size()));
+ message = message.replace("{name}",playerName);
+ e.setJoinMessage(message);
+
+ Location lobbyLoc = WarpAPI.getWarpLocation("游戏大厅");
+ if(player.hasPlayedBefore()){
+ player.teleport(lobbyLoc);
+ } else {
+ Bukkit.getScheduler().scheduleSyncDelayedTask(TagMain.inst(), () -> {
+ player.teleport(lobbyLoc);
+ }, 10L);
+ }
+ player.setGameMode(GameMode.ADVENTURE);
+ player.setGlowing(false);
+ player.setExp(0.0F);
+ player.setLevel(0);
+ player.setHealth(20.0);
+ player.setFoodLevel(20);
+ player.getInventory().clear();
+ player.getInventory().setArmorContents(null);
+ player.setWalkSpeed(0.35F);
+ player.getInventory().setItem(0, StackUtil.observerOn());
+ player.getInventory().setItem(8, StackUtil.quitStack());
+ if(player.isOp()){
+ player.getInventory().setItem(4, StackUtil.forciblyStack());
+ }
+ new SideBar().updateScoreboard();
+ }
+
+ @EventHandler
+ public void onQuitLobby(PlayerQuitEvent e) {
+ e.setQuitMessage(null);
+ Player player = e.getPlayer();
+ String playerName = player.getName();
+ GameManager game = TagMain.getGame();
+ if(game.gameState != GameState.LOBBY){
+ return;
+ }
+ String message = MessageUtil.getMessage("quit_message").replace("{online}",String.valueOf(Bukkit.getOnlinePlayers().size()));
+ message = message.replace("{name}",playerName);
+ e.setQuitMessage(message);
+ new SideBar().updateScoreboard();
+ }
+
+
+ @EventHandler
+ public void onChat(AsyncPlayerChatEvent e) {
+ Player p = e.getPlayer();
+ if ((e.getMessage().contains("&k"))||(e.getMessage().contains("§k"))) {
+ e.getMessage().replace("&k", "").replace("§k", "");
+ }
+ e.setMessage(ChatColor.translateAlternateColorCodes('&', e.getMessage()));
+ String prefix = "§7[§2§lPlayer§7]";
+ if(p.isOp()){
+ prefix = "§7[§c§lAdmin§7]";
+ }
+ String format = prefix+"§f"+p.getName()+"§8§l > §r"+e.getMessage();
+ e.setFormat(format);
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e){
+ int slot = e.getSlot();
+ Player player = (Player) e.getWhoClicked();
+ e.setCancelled(true);
+ InventoryType inventoryType = e.getInventory().getType();
+ if(inventoryType.equals(InventoryType.CRAFTING)){
+ if(slot == 0 && player.isOp()){
+ // 执行强制开始
+ LobbyCounter.forceStartGame();
+ }
+ if(slot == 8){
+ player.playSound(player.getLocation(), Sound.BLOCK_COMPARATOR_CLICK,1,1);
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ DemonAPI.sendPlayerToBCServer(player, Config.backServer);
+ }
+ }.runTaskLater(TagMain.inst(), 5L);
+ }
+ }
+ }
+ @EventHandler
+ public void onUse(PlayerInteractEvent e){
+ Player player = e.getPlayer();
+ if(e.getHand() == EquipmentSlot.HAND) {
+ if(e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ ItemStack item = player.getInventory().getItemInMainHand();
+ if (!DemonAPI.itemIsNull(item)) {
+ Material material = item.getType();
+ if (material.equals(Material.BED)) {
+ player.playSound(player.getLocation(), Sound.BLOCK_COMPARATOR_CLICK, 1, 1);
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ DemonAPI.sendPlayerToBCServer(player, Config.backServer);
+ }
+ }.runTaskLater(TagMain.inst(), 5L);
+ }
+ if (material.equals(Material.DIAMOND)) {
+ GameManager game = TagMain.getGame();
+ if (game.gameState != GameState.LOBBY) {
+ return;
+ }
+ if (!player.isOp()) {
+ return;
+ }
+ // 执行强制开始命令
+ LobbyCounter.forceStartGame();
+ }
+ if (material.equals(Material.SLIME_BALL)) {
+ player.getInventory().clear();
+ player.setAllowFlight(true);
+ player.setFlying(true);
+ player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 999999999, 1),true);
+ MessageUtil.sendMessage(player, "observer_on");
+ player.playSound(player.getLocation(),Sound.BLOCK_COMPARATOR_CLICK,0.8f,1.2f);
+ player.getInventory().setItem(0, StackUtil.observerOff());
+ }
+ if (material.equals(Material.MAGMA_CREAM)) {
+ player.getInventory().setItem(0, StackUtil.observerOn());
+ player.getInventory().setItem(8, StackUtil.quitStack());
+ player.setAllowFlight(false);
+ player.setFlying(false);
+ player.getActivePotionEffects().forEach((potionEffect) -> {
+ player.removePotionEffect(potionEffect.getType());
+ });
+ player.teleport(WarpAPI.getWarpLocation("游戏大厅"));
+ MessageUtil.sendMessage(player, "observer_off");
+ GameManager gameManager = TagMain.getGame();
+ if(gameManager.getGameState() == GameState.LOBBY){
+ if(player.isOp()){
+ player.getInventory().setItem(4, StackUtil.forciblyStack());
+ }
+ }
+ player.playSound(player.getLocation(),Sound.BLOCK_COMPARATOR_CLICK,0.8f,1.2f);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/listener/ProtectListener.java b/src/main/java/com/yaohun/tnttag/listener/ProtectListener.java
new file mode 100644
index 0000000..4a539d3
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/listener/ProtectListener.java
@@ -0,0 +1,101 @@
+package com.yaohun.tnttag.listener;
+
+import com.yaohun.tnttag.util.MessageUtil;
+import com.yaohun.tnttag.util.StackUtil;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.block.LeavesDecayEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.weather.WeatherChangeEvent;
+
+public class ProtectListener implements Listener {
+
+ @EventHandler
+ public void onBreak(BlockBreakEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onPlace(BlockPlaceEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onCreateurs(CreatureSpawnEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onDamage(EntityDamageEvent e){
+ if(e.getEntity() instanceof Player){
+ e.setDamage(0.0);
+ }
+ }
+
+ @EventHandler
+ public void onWeather(WeatherChangeEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onLeaves(LeavesDecayEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onDrop(PlayerDropItemEvent e){
+ e.setCancelled(true);
+ }
+
+ @EventHandler
+ public void onDamage(EntityDamageByEntityEvent e){
+ if(e.getEntity() instanceof Player){
+ if(e.getEntity().getLocation().getY() >= 130){
+ e.setCancelled(true);
+ }
+ }
+ }
+
+ // 位置1和位置2(你可以从配置文件中加载)
+ private final Location pos1 = new Location(Bukkit.getWorld("world"), 4, 9, 304);
+ private final Location pos2 = new Location(Bukkit.getWorld("world"), -303, 180, -3);
+
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ Player player = event.getPlayer();
+ Location loc = player.getLocation();
+
+ // 获取当前坐标
+ double x = loc.getX();
+ double y = loc.getY();
+ double z = loc.getZ();
+
+ // 计算区域边界(自动处理 pos1/pos2 顺序)
+ double minX = Math.min(pos1.getX(), pos2.getX());
+ double maxX = Math.max(pos1.getX(), pos2.getX());
+ double minY = Math.min(pos1.getY(), pos2.getY());
+ double maxY = Math.max(pos1.getY(), pos2.getY());
+ double minZ = Math.min(pos1.getZ(), pos2.getZ());
+ double maxZ = Math.max(pos1.getZ(), pos2.getZ());
+
+ // 判断是否越界
+ if (x < minX || x > maxX || y < minY || y > maxY || z < minZ || z > maxZ) {
+ player.teleport(WarpAPI.getWarpLocation("游戏场地"));
+ player.playSound(player.getLocation(), Sound.BLOCK_COMPARATOR_CLICK,0.8f,1.2f);
+ }
+ }
+
+
+
+}
diff --git a/src/main/java/com/yaohun/tnttag/manage/GameManager.java b/src/main/java/com/yaohun/tnttag/manage/GameManager.java
new file mode 100644
index 0000000..9c7de68
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/manage/GameManager.java
@@ -0,0 +1,136 @@
+package com.yaohun.tnttag.manage;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.counter.RestartCounter;
+import com.yaohun.tnttag.util.*;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+public class GameManager {
+
+ private final TagMain plugin;
+ // 存活玩家集合(正在游戏中、未被淘汰)
+ private final Set alivePlayers = new HashSet<>();
+ // 所有参与过该局的玩家(用于重置)
+ private final Set allPlayers = new HashSet<>();
+ // RoundManager:每一轮的逻辑控制器
+ private final RoundManager roundManager;
+ // 当前游戏状态(等待、开始中、进行中、结束)
+ public GameState gameState;
+
+ public GameManager(TagMain plugin){
+ this.plugin = plugin;
+ gameState = GameState.LOBBY;
+ this.roundManager = new RoundManager(this, plugin);
+ }
+
+ public void setGameState(GameState gameState) {
+ this.gameState = gameState;
+ }
+
+ /**
+ * 添加玩家到游戏中(只能在等待状态加入)
+ */
+ public void addPlayer(Player player) {
+ alivePlayers.add(player);
+ allPlayers.add(player);
+ }
+
+ /**
+ * 玩家离开游戏,若为最后一人则直接结束游戏
+ */
+ public void removePlayer(Player player) {
+ alivePlayers.remove(player);
+ allPlayers.remove(player);
+
+ if (alivePlayers.size() <= 1 && gameState == GameState.INGAME) {
+ // 结束游戏 endGame();
+ }
+ }
+
+ public boolean isAlive(Player player) {
+ return alivePlayers.contains(player);
+ }
+
+ /**
+ * 淘汰玩家(设为观察者模式并移出活跃列表)
+ */
+ public void eliminatePlayer(Player player) {
+ alivePlayers.remove(player);
+ String playerName = player.getName();
+ String message = "§f[§c系统§f] §a玩家 §6"+playerName+" §a因未在规定时间传递炸弹=v= §c§l已被炸死!";
+ Bukkit.broadcastMessage(message);
+ player.teleport(WarpAPI.getWarpLocation("游戏大厅"));
+ player.getInventory().clear();
+ String time = DemonAPI.getTime("HH:mm:ss");
+ int aliveSize = alivePlayers.size();
+ if(aliveSize == 2){
+ String rankingRewards = MessageUtil.getMessage("ranking_rewards_3").replace("{time}", time);
+ Bukkit.broadcastMessage(rankingRewards.replace("{name}", playerName));
+ }else if(aliveSize == 1){
+ String rankingRewards = MessageUtil.getMessage("ranking_rewards_2").replace("{time}", time);
+ Bukkit.broadcastMessage(rankingRewards.replace("{name}", playerName));
+ }
+ player.sendMessage("§f[§c系统§f] §b你被淘汰了,你可以选择留下继续观战~(●′ω`●)~");
+ player.getInventory().setItem(0, StackUtil.observerOn());
+ player.getInventory().setItem(8, StackUtil.quitStack());
+ player.setAllowFlight(false);
+ player.setFlying(false);
+ player.setGlowing(false);
+ player.getActivePotionEffects().forEach((potionEffect) -> {
+ player.removePotionEffect(potionEffect.getType());
+ });
+ }
+
+ /**
+ * 游戏结束逻辑,广播胜者、重置状态
+ */
+ public void endGame() {
+ gameState = GameState.RESTART;
+ roundManager.stopRound();
+
+ Player winner = alivePlayers.stream().findFirst().orElse(null);
+ if (winner != null) {
+ String playerName = winner.getName();
+ String rankingRewards = MessageUtil.getMessage("ranking_rewards_1").replace("{time}", DemonAPI.getTime("HH:mm:ss"));
+ Bukkit.broadcastMessage(rankingRewards.replace("{name}", playerName));
+ for (Player all : Bukkit.getOnlinePlayers()) {
+ all.sendTitle("§c§l游戏结束!", "§b" + playerName + " §6获得本场胜利!", 10, 80, 20);
+ }
+ }
+ RestartCounter.restart();
+ }
+
+ /**
+ * 正式开始游戏,传送玩家到战场并启动第一轮
+ */
+ public void startGame() {
+ new SideBar().updateScoreboard();
+ roundManager.startNextRound(); // 启动第一轮
+ for (Player player : getAlivePlayers()){
+ roundManager.addBossBar(player);
+ }
+ }
+
+ // ---------------------- Getter 方法 ----------------------
+
+ public Set getAlivePlayers() {
+ return alivePlayers;
+ }
+
+ public GameState getGameState() {
+ return gameState;
+ }
+
+ public RoundManager getRoundManager() {
+ return roundManager;
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/manage/RoundManager.java b/src/main/java/com/yaohun/tnttag/manage/RoundManager.java
new file mode 100644
index 0000000..1ddd57b
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/manage/RoundManager.java
@@ -0,0 +1,231 @@
+package com.yaohun.tnttag.manage;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.counter.RestartCounter;
+import com.yaohun.tnttag.util.MessageUtil;
+import com.yaohun.tnttag.util.SideBar;
+import com.yaohun.tnttag.util.SpeedEffectApplier;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.RandomUtil;
+import me.Demon.DemonWarps.WarpAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.boss.BossBar;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+import java.util.*;
+
+public class RoundManager {
+
+ private final GameManager gameManager;
+ private final TagMain plugin;
+
+ private BossBar bossBar;
+
+ private int roundNumber = 0;
+ private int countdownSeconds = 30;
+ private int taskId = -1;
+
+ private final Set tntPlayers = new HashSet<>();
+
+ public RoundManager(GameManager gameManager, TagMain plugin) {
+ this.gameManager = gameManager;
+ this.plugin = plugin;
+ this.bossBar = Bukkit.createBossBar("倒计时", BarColor.RED, BarStyle.SEGMENTED_10);
+ }
+
+ public void setBossBarProgress(double progress,int second) {
+ if(progress < 0){
+ progress = 0;
+ } else if(progress > 1){
+ progress = 1;
+ }
+ bossBar.setProgress(progress);
+ bossBar.setTitle("§a§l炸弹传递§c§l·§e§l即将在 §b§l"+second+"秒 §e§l后爆炸!");
+ double chance = RandomUtil.getRandomDouble(0,100,1);
+ if(chance >= 75){
+ bossBar.setColor(BarColor.PINK);
+ }else if(chance >= 50){
+ bossBar.setColor(BarColor.BLUE);
+ }else if(chance >= 25){
+ bossBar.setColor(BarColor.RED);
+ }else{
+ bossBar.setColor(BarColor.YELLOW);
+ }
+ }
+
+ public void addBossBar(Player player){
+ if(!bossBar.getPlayers().contains(player)){
+ bossBar.addPlayer(player);
+ }
+ }
+
+ public int getRoundNumber() {
+ return roundNumber;
+ }
+
+ public void startNextRound() {
+ roundNumber++;
+ if (roundNumber <= 3) {
+ countdownSeconds = 30;
+ } else if (roundNumber <= 6) {
+ countdownSeconds = 25;
+ } else if (roundNumber <= 9) {
+ countdownSeconds = 20;
+ } else {
+ countdownSeconds = 15;
+ }
+ // 刷新记分板
+ new SideBar().updateScoreboard();
+ int survivingPlayers = gameManager.getAlivePlayers().size();
+ if(survivingPlayers < 5){
+ for (Player all : gameManager.getAlivePlayers()) {
+ all.teleport(WarpAPI.getWarpLocation("游戏场地"));
+ MessageUtil.playSound(Sound.ENTITY_ENDERDRAGON_GROWL);
+ }
+ }
+ selectInitialTntPlayers();
+ for (Player player : gameManager.getAlivePlayers()){
+ if(isTnt(player)){
+ MessageUtil.sendMessage(player,"§f[§c系统§f] §a糟糕你被抽中成为了本轮的§c§l炸弹客§a! §e(左键传递给其他人)", Sound.ENTITY_CAT_AMBIENT);
+ player.setGlowing(false);
+ } else {
+ MessageUtil.sendMessage(player,"§f[§c系统§f] §a快跑起来,远离头上炸弹的玩家,否则会被炸死!", Sound.ENTITY_CAT_AMBIENT);
+ player.setGlowing(true);
+ }
+ }
+ startCountdownTask();
+ }
+
+ public void stopRound() {
+ if (taskId != -1) {
+ Bukkit.getScheduler().cancelTask(taskId);
+ taskId = -1;
+ }
+ tntPlayers.clear();
+ }
+
+ public boolean isTnt(Player player) {
+ return tntPlayers.contains(player);
+ }
+
+ public void passTnt(Player from, Player to) {
+ if (!tntPlayers.contains(from)) {
+ return;
+ }
+
+ tntPlayers.remove(from);
+ tntPlayers.add(to);
+
+ // 传递者TNT状态提示
+ from.getInventory().clear();
+ from.getInventory().setHelmet(null);
+ SpeedEffectApplier.applyInfiniteSpeed(from, 1);
+ from.setAllowFlight(false);
+ MessageUtil.sendMessage(from,"§f[§c系统§f] §b炸弹传递成功!尽快远离§6"+to.getName()+"§b他现在被定住了!",Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
+ from.setGlowing(true);
+
+ // 被传递者TNT状态提示
+ for (int i = 0; i < 9;i++){
+ to.getInventory().setItem(i, new ItemStack(Material.TNT));
+ }
+ to.getInventory().setHelmet(new ItemStack(Material.TNT));
+ to.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 70, 1, false, false));
+ to.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 50, 5, false, false));
+ SpeedEffectApplier.applyInfiniteSpeed(to, 3);
+ to.setAllowFlight(true);
+ MessageUtil.sendMessage(to,"§f[§c系统§f] §a谢特~§6"+from.getName()+"§a将炸弹传递给了你!",Sound.ENTITY_BLAZE_DEATH);
+ to.setGlowing(false);
+ }
+
+ public Set getTntPlayers() {
+ return tntPlayers;
+ }
+
+ private void startCountdownTask() {
+ taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
+ int seconds = countdownSeconds;
+ @Override
+ public void run() {
+ if (seconds <= 0) {
+ handleExplosions();
+ stopRound();
+ // 检查是否游戏结束
+ if (gameManager.getAlivePlayers().size() <= 1) {
+ gameManager.endGame();
+ } else {
+ startNextRound();
+ }
+ return;
+ }
+ // 显示倒计时(可选:BossBar)
+ setBossBarProgress(seconds / (double) countdownSeconds,seconds);
+ seconds--;
+ }
+ }, 0L, 20L); // 每秒一次
+ }
+
+ private void handleExplosions() {
+ Set eliminated = new HashSet<>(tntPlayers);
+ for (Player player : eliminated) {
+ gameManager.eliminatePlayer(player);
+ // 不破坏方块
+ player.getWorld().createExplosion(player.getLocation(), 0F);
+ }
+ }
+ private int calculateTntCount(int aliveCount, int roundNumber) {
+ // 决赛期直接限制为1个
+ if (aliveCount <= 3) {
+ return 1;
+ }
+ // 基础分配比例,根据轮次略作调整(可调节)
+ double ratio;
+ if (roundNumber == 1) {
+ ratio = 0.12; // 开局温和一点
+ } else if (roundNumber <= 4) {
+ ratio = 0.16;
+ } else if (roundNumber <= 7) {
+ ratio = 0.14;
+ } else {
+ ratio = 0.10;
+ }
+
+ // 计算初步TNT数量
+ int tntCount = (int) Math.floor(aliveCount * ratio);
+
+ // 强制下限为1,上限为 aliveCount - 1
+ tntCount = Math.max(1, tntCount);
+ tntCount = Math.min(tntCount, aliveCount - 1);
+
+ return tntCount;
+ }
+
+
+ private void selectInitialTntPlayers() {
+ List alive = new ArrayList<>(gameManager.getAlivePlayers());
+ int aliveCount = alive.size();
+ int roundNumber = gameManager.getRoundManager().getRoundNumber();
+
+ int tntCount = calculateTntCount(aliveCount, roundNumber);
+ Collections.shuffle(alive);
+
+ List stringList = new ArrayList<>();
+ for (int i = 0; i < tntCount && i < alive.size(); i++) {
+ Player player = alive.get(i);
+ tntPlayers.add(player);
+ // TNT状态提示
+ player.getInventory().setHelmet(new ItemStack(Material.TNT));
+ SpeedEffectApplier.applyInfiniteSpeed(player, 3);
+ player.setAllowFlight(true);
+ stringList.add(player.getName());
+ }
+ String bombsAnnounced = MessageUtil.getMessage("bombs_announced").replace("{listName}", String.join("§a, §e",stringList));
+ Bukkit.broadcastMessage(bombsAnnounced);
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/task/CountdownTask.java b/src/main/java/com/yaohun/tnttag/task/CountdownTask.java
new file mode 100644
index 0000000..249c87b
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/task/CountdownTask.java
@@ -0,0 +1,4 @@
+package com.yaohun.tnttag.task;
+
+public class CountdownTask {
+}
diff --git a/src/main/java/com/yaohun/tnttag/task/TntSmokeEffectTask.java b/src/main/java/com/yaohun/tnttag/task/TntSmokeEffectTask.java
new file mode 100644
index 0000000..bd0cd1a
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/task/TntSmokeEffectTask.java
@@ -0,0 +1,32 @@
+package com.yaohun.tnttag.task;
+
+import com.yaohun.tnttag.manage.GameManager;
+import com.yaohun.tnttag.util.GameState;
+import org.bukkit.Effect;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class TntSmokeEffectTask extends BukkitRunnable {
+
+ private final GameManager gameManager;
+
+ public TntSmokeEffectTask(GameManager gameManager) {
+ this.gameManager = gameManager;
+ }
+
+ @Override
+ public void run() {
+ // 游戏未开始时跳过
+ GameState gameState = gameManager.gameState;
+ if (gameState != GameState.INGAME) {
+ return;
+ }
+ // 遍历所有 TNT 玩家
+ for (Player player : gameManager.getRoundManager().getTntPlayers()) {
+ // 播放烟雾粒子(1.12.2 推荐使用 Spigot API)
+ Location loc = player.getLocation().add(0, 0.5, 0);
+ player.getWorld().spigot().playEffect(loc, Effect.CLOUD, 0, 0, 0.15F, 0.15F, 0.15F, 0.15F, 5, 30);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/tnttag/util/GameState.java b/src/main/java/com/yaohun/tnttag/util/GameState.java
new file mode 100644
index 0000000..14e46a3
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/GameState.java
@@ -0,0 +1,7 @@
+package com.yaohun.tnttag.util;
+
+public enum GameState {
+ LOBBY, // 等待玩家
+ INGAME, // 游戏已开始
+ RESTART // 游戏即将重启
+}
diff --git a/src/main/java/com/yaohun/tnttag/util/MessageUtil.java b/src/main/java/com/yaohun/tnttag/util/MessageUtil.java
new file mode 100644
index 0000000..9c4a396
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/MessageUtil.java
@@ -0,0 +1,61 @@
+package com.yaohun.tnttag.util;
+
+import com.yaohun.tnttag.TagMain;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class MessageUtil {
+
+ private static HashMap messageMap = new HashMap<>();
+
+ public static void init(TagMain plugin) {
+ File file = new File(plugin.getDataFolder(), "message.yml");
+ if (!file.exists()) {
+ plugin.saveResource("message.yml", false);
+ }
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String key : config.getKeys(false)){
+ ConfigurationSection section = config.getConfigurationSection(key);
+ if(section == null){
+ continue;
+ }
+ for (String key2 : section.getKeys(false)){
+ String message = section.getString(key2).replace("&", "§");
+ messageMap.put(key2, message);
+ }
+ }
+ }
+
+ public static String getMessage(String key){
+ if(messageMap.containsKey(key)){
+ return messageMap.get(key);
+ }
+ return "§c缺少参数."+key;
+ }
+
+ public static void sendMessage(CommandSender sender, String message, Sound sound){
+ sender.sendMessage(message);
+ if(sender instanceof Player){
+ Player player = (Player) sender;
+ player.playSound(player.getLocation(),sound,0.8f,1.2f);
+ }
+ }
+
+ public static void sendMessage(CommandSender sender, String key){
+ sender.sendMessage(getMessage(key));
+ }
+
+ public static void playSound(Sound sound){
+ for (Player player : Bukkit.getOnlinePlayers()){
+ player.playSound(player.getLocation(),sound,1.0f,1.0f);
+ }
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/util/PlayerState.java b/src/main/java/com/yaohun/tnttag/util/PlayerState.java
new file mode 100644
index 0000000..58fc28f
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/PlayerState.java
@@ -0,0 +1,6 @@
+package com.yaohun.tnttag.util;
+
+public enum PlayerState {
+ SURVIVE,
+ DEATH
+}
diff --git a/src/main/java/com/yaohun/tnttag/util/SideBar.java b/src/main/java/com/yaohun/tnttag/util/SideBar.java
new file mode 100644
index 0000000..6a99d55
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/SideBar.java
@@ -0,0 +1,51 @@
+package com.yaohun.tnttag.util;
+
+import com.yaohun.tnttag.TagMain;
+import com.yaohun.tnttag.manage.GameManager;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.DisplaySlot;
+import org.bukkit.scoreboard.Objective;
+import org.bukkit.scoreboard.Scoreboard;
+
+public class SideBar {
+
+ public void updateScoreboard() {
+ Scoreboard board = Bukkit.getScoreboardManager().getNewScoreboard();
+ Objective objective = board.registerNewObjective("AAA", "BBB");
+ objective.setDisplaySlot(DisplaySlot.SIDEBAR);
+ GameManager gameManager = TagMain.getGame();
+ if (gameManager.gameState == GameState.LOBBY) {
+ objective.setDisplayName("§c====§6§lTNT-Tag§c====");
+ objective.getScore("§A§r").setScore(7);
+ objective.getScore("§e地图: §a宗门领地").setScore(6);
+ int online = Bukkit.getOnlinePlayers().size();
+ objective.getScore("§e地图: §a"+online+"/60").setScore(5);
+ objective.getScore("§A").setScore(4);
+ objective.getScore("§6等待中 ...").setScore(3);
+ objective.getScore("§f").setScore(2);
+ objective.getScore("§A§l咯吱窝:").setScore(1);
+ objective.getScore("§e§l mc.163.com").setScore(0);
+ for (Player all : Bukkit.getOnlinePlayers()) {
+ all.setScoreboard(board);
+ }
+ return;
+ }
+ if (gameManager.gameState == GameState.INGAME) {
+ objective.setDisplayName("§c====§6§lTNT-Tag§c====");
+ objective.getScore("§A§r").setScore(6);
+ objective.getScore("§b§l存活: §6" + gameManager.getAlivePlayers().size()+"名").setScore(5);
+ int number = gameManager.getRoundManager().getRoundNumber();
+ objective.getScore("§b§l回合: §6" + number).setScore(4);
+ int bombkeeper = gameManager.getRoundManager().getTntPlayers().size();
+ objective.getScore("§b§l炸弹: §6" + bombkeeper+"颗").setScore(3);
+ objective.getScore("§f").setScore(2);
+ objective.getScore("§A§l咯吱窝:").setScore(1);
+ objective.getScore("§e§l mc.163.com").setScore(0);
+ for (Player all : Bukkit.getOnlinePlayers()) {
+ all.setScoreboard(board);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/util/SpeedEffectApplier.java b/src/main/java/com/yaohun/tnttag/util/SpeedEffectApplier.java
new file mode 100644
index 0000000..a36ff20
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/SpeedEffectApplier.java
@@ -0,0 +1,34 @@
+package com.yaohun.tnttag.util;
+
+import org.bukkit.entity.Player;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+public class SpeedEffectApplier {
+
+ /**
+ * 给玩家添加无限持续时间的速度药水效果
+ *
+ * @param player 玩家对象
+ * @param amplifier 等级(0 = Speed I,1 = Speed II,依此类推)
+ */
+ public static void applyInfiniteSpeed(Player player, int amplifier) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+ player.removePotionEffect(PotionEffectType.SPEED);
+
+ PotionEffect speed = new PotionEffect(PotionEffectType.SPEED, Integer.MAX_VALUE, amplifier, false, false);
+ player.addPotionEffect(speed);
+ }
+
+ /**
+ * 移除速度药水效果
+ */
+ public static void clearSpeed(Player player) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+ player.removePotionEffect(PotionEffectType.SPEED);
+ }
+}
diff --git a/src/main/java/com/yaohun/tnttag/util/StackUtil.java b/src/main/java/com/yaohun/tnttag/util/StackUtil.java
new file mode 100644
index 0000000..d80222d
--- /dev/null
+++ b/src/main/java/com/yaohun/tnttag/util/StackUtil.java
@@ -0,0 +1,41 @@
+package com.yaohun.tnttag.util;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class StackUtil {
+
+ public static ItemStack quitStack(){
+ ItemStack stack = new ItemStack(Material.BED);
+ stack.setDurability((short) 14);
+ ItemMeta meta = stack.getItemMeta();
+ meta.setDisplayName("§c§l退出游戏");
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ public static ItemStack forciblyStack(){
+ ItemStack stack = new ItemStack(Material.DIAMOND);
+ ItemMeta meta = stack.getItemMeta();
+ meta.setDisplayName("§b§l强制开始");
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ public static ItemStack observerOn(){
+ ItemStack stack = new ItemStack(Material.SLIME_BALL);
+ ItemMeta meta = stack.getItemMeta();
+ meta.setDisplayName("§a§l旁观§7(右键开启)");
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ public static ItemStack observerOff(){
+ ItemStack stack = new ItemStack(Material.MAGMA_CREAM);
+ ItemMeta meta = stack.getItemMeta();
+ meta.setDisplayName("§c§l旁观§7(右键关闭)");
+ stack.setItemMeta(meta);
+ return stack;
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..3b14185
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,2 @@
+requirementPlayer: 30
+backServer: loginlobby01
\ No newline at end of file
diff --git a/src/main/resources/message.yml b/src/main/resources/message.yml
new file mode 100644
index 0000000..f5b651e
--- /dev/null
+++ b/src/main/resources/message.yml
@@ -0,0 +1,11 @@
+message:
+ insufficient_number_of_people: "§f[§c系统§f] §a还需要 §e{value}名 §a玩家加入,游戏才会开始。"
+ start_countdown: "§f[§c系统§f] §a游戏将在 §e%seconds%秒 §a后开始."
+ join_message: "§f[§c系统§f] §b欢迎玩家 §6{name} §b加入了烫手山芋 [§6{online} §b/ §660§b]"
+ quit_message: "§f[§c系统§f] §b玩家 §6{name} §b离开了烫手山芋 [§6{online} §b/ §660§b]"
+ observer_on: "§f[§c系统§f] §a☃ §b开启观察者模式!"
+ observer_off: "§f[§c系统§f] §c☃ §b关闭观察者模式!"
+ bombs_announced: "§f[§c系统§f] §a本轮炸弹客名单: §e{listName} §c(远离他们)"
+ ranking_rewards_3: "§7[§c§l公告§7] §6恭喜玩家§2{name}§6在 §d{time} §6炸弹传递获得第三名!奖励内容: §7[§d非献祭§7]§c十万年灵环抽奖箱"
+ ranking_rewards_2: "§7[§c§l公告§7] §6恭喜玩家§2{name}§6在 §d{time} §6炸弹传递获得第二名!奖励内容: §7[§d非献祭§7]§c十万年灵环抽奖箱"
+ ranking_rewards_1: "§7[§c§l公告§7] §6恭喜玩家§2{name}§6在 §d{time} §6炸弹传递获得游戏胜利!奖励内容: §7[§d献祭§7]§c十万年灵环抽奖箱"
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..9fca31e
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: AuTntTag
+main: com.yaohun.tnttag.TagMain
+version: 1.0.0
+author: yaohun
+depend:
+ - DemonAPI
+commands:
+ game:
+ forcestart:
\ No newline at end of file