Compare commits
15 Commits
2944f192db
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ab0db2f1a | |||
| ea8124e0ed | |||
| 6f489eb660 | |||
| 53fb52e294 | |||
| b2011cecbb | |||
| a375365d29 | |||
| 3015ba5a7c | |||
| 4d09482fb1 | |||
| 57c24b1dce | |||
| 529802f6c5 | |||
| f8b8065c37 | |||
| c5a3b66e5f | |||
| da974f2238 | |||
| 42ed9e8bc9 | |||
| 4712ed8723 |
32
.gitea/workflows/package.yaml
Normal file
32
.gitea/workflows/package.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Build Minecraft Plugin
|
||||||
|
run-name: ${{ gitea.actor }} is building Minecraft plugin 🚀
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build with Maven
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up JDK 21 and enable Maven cache
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: 'liberica'
|
||||||
|
java-version: '21'
|
||||||
|
cache: 'maven'
|
||||||
|
|
||||||
|
- name: Set up Maven
|
||||||
|
uses: stCarolas/setup-maven@v5
|
||||||
|
with:
|
||||||
|
maven-version: 3.9.11
|
||||||
|
|
||||||
|
- name: Build and package with Maven
|
||||||
|
run: mvn -B package
|
||||||
|
|
||||||
|
- name: Upload built artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: maven-artifacts
|
||||||
|
path: target/spigot/*.jar
|
||||||
9
pom.xml
9
pom.xml
@@ -22,7 +22,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.13.0</version>
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>${java.version}</source>
|
<source>${java.version}</source>
|
||||||
<target>${java.version}</target>
|
<target>${java.version}</target>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.5.3</version>
|
<version>3.6.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -40,6 +40,11 @@
|
|||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<finalName>${project.artifactId}-spigot-${project.version}</finalName>
|
||||||
|
<outputDirectory>${project.build.directory}/spigot</outputDirectory>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
<resources>
|
<resources>
|
||||||
|
|||||||
137
src/main/java/org/xgqy/survival/AccountManager.java
Normal file
137
src/main/java/org/xgqy/survival/AccountManager.java
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package org.xgqy.survival;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class AccountManager {
|
||||||
|
private final Survival plugin;
|
||||||
|
private final File accountsFile;
|
||||||
|
private final FileConfiguration accountsConfig;
|
||||||
|
private final Map<UUID, PlayerAccount> accounts = new HashMap<>();
|
||||||
|
private final Map<UUID, Boolean> loggedInPlayers = new HashMap<>();
|
||||||
|
|
||||||
|
public AccountManager(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.accountsFile = new File(plugin.getDataFolder(), "accounts.yml");
|
||||||
|
|
||||||
|
if (!accountsFile.exists()) {
|
||||||
|
plugin.getDataFolder().mkdirs();
|
||||||
|
try {
|
||||||
|
accountsFile.createNewFile();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accountsConfig = YamlConfiguration.loadConfiguration(accountsFile);
|
||||||
|
loadAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAccounts() {
|
||||||
|
for (String key : accountsConfig.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(key);
|
||||||
|
|
||||||
|
// 手动读取字段并构建PlayerAccount对象
|
||||||
|
String name = accountsConfig.getString(key + ".username");
|
||||||
|
String passwordHash = accountsConfig.getString(key + ".passwordHash");
|
||||||
|
long lastLoginTime = accountsConfig.getLong(key + ".lastLoginTime", 0);
|
||||||
|
|
||||||
|
if (name != null && passwordHash != null) {
|
||||||
|
PlayerAccount account = new PlayerAccount(name, passwordHash);
|
||||||
|
account.setLastLoginTime(lastLoginTime);
|
||||||
|
accounts.put(uuid, account);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLogger().warning("Invalid UUID format: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAccounts() {
|
||||||
|
for (Map.Entry<UUID, PlayerAccount> entry : accounts.entrySet()) {
|
||||||
|
String uuidStr = entry.getKey().toString();
|
||||||
|
PlayerAccount account = entry.getValue();
|
||||||
|
|
||||||
|
// 手动存储每个字段
|
||||||
|
accountsConfig.set(uuidStr + ".username", account.getUsername());
|
||||||
|
accountsConfig.set(uuidStr + ".passwordHash", account.getPasswordHash());
|
||||||
|
accountsConfig.set(uuidStr + ".lastLoginTime", account.getLastLoginTime());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
accountsConfig.save(accountsFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegistered(UUID uuid) {
|
||||||
|
return accounts.containsKey(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean registerAccount(Player player, String password) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
|
||||||
|
if (isRegistered(uuid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String hashedPassword = hashPassword(password);
|
||||||
|
PlayerAccount account = new PlayerAccount(player.getName(), hashedPassword);
|
||||||
|
accounts.put(uuid, account);
|
||||||
|
saveAccounts();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean login(Player player, String password) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
|
||||||
|
if (!isRegistered(uuid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerAccount account = accounts.get(uuid);
|
||||||
|
String hashedPassword = hashPassword(password);
|
||||||
|
|
||||||
|
if (account.getPasswordHash().equals(hashedPassword)) {
|
||||||
|
account.setLastLoginTime(System.currentTimeMillis());
|
||||||
|
loggedInPlayers.put(uuid, true);
|
||||||
|
saveAccounts();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logout(Player player) {
|
||||||
|
loggedInPlayers.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoggedIn(Player player) {
|
||||||
|
return loggedInPlayers.getOrDefault(player.getUniqueId(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String hashPassword(String password) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hashedBytes = md.digest(password.getBytes());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : hashedBytes) {
|
||||||
|
sb.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("SHA-256 algorithm not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/org/xgqy/survival/FriendData.java
Normal file
31
src/main/java/org/xgqy/survival/FriendData.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package org.xgqy.survival;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
// 实现Serializable接口并添加版本号(关键修复)
|
||||||
|
public class FriendData implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||||
|
|
||||||
|
private Map<UUID, List<UUID>> friends;
|
||||||
|
private Map<UUID, List<UUID>> waiting;
|
||||||
|
|
||||||
|
// getter和setter
|
||||||
|
public Map<UUID, List<UUID>> getFriends() {
|
||||||
|
return friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFriends(Map<UUID, List<UUID>> friends) {
|
||||||
|
this.friends = friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<UUID, List<UUID>> getWaiting() {
|
||||||
|
return waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaiting(Map<UUID, List<UUID>> waiting) {
|
||||||
|
this.waiting = waiting;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/main/java/org/xgqy/survival/PlayerAccount.java
Normal file
57
src/main/java/org/xgqy/survival/PlayerAccount.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package org.xgqy.survival;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PlayerAccount implements ConfigurationSerializable {
|
||||||
|
private String username;
|
||||||
|
private String passwordHash;
|
||||||
|
private long registerTime;
|
||||||
|
private long lastLoginTime;
|
||||||
|
|
||||||
|
public PlayerAccount(String username, String passwordHash) {
|
||||||
|
this.username = username;
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
this.registerTime = System.currentTimeMillis();
|
||||||
|
this.lastLoginTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerAccount(Map<String, Object> map) {
|
||||||
|
this.username = (String) map.get("username");
|
||||||
|
this.passwordHash = (String) map.get("passwordHash");
|
||||||
|
this.registerTime = (long) map.get("registerTime");
|
||||||
|
this.lastLoginTime = (long) map.get("lastLoginTime");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> serialize() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("username", username);
|
||||||
|
map.put("passwordHash", passwordHash);
|
||||||
|
map.put("registerTime", registerTime);
|
||||||
|
map.put("lastLoginTime", lastLoginTime);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordHash() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRegisterTime() {
|
||||||
|
return registerTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastLoginTime() {
|
||||||
|
return lastLoginTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastLoginTime(long lastLoginTime) {
|
||||||
|
this.lastLoginTime = lastLoginTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,10 @@ import java.util.UUID;
|
|||||||
public class PlayerTags {
|
public class PlayerTags {
|
||||||
private final Survival plugin;
|
private final Survival plugin;
|
||||||
private final File tagsFile;
|
private final File tagsFile;
|
||||||
// 原有:玩家-称号列表映射
|
|
||||||
private final Map<UUID, List<String>> playerTags = new HashMap<>();
|
private final Map<UUID, List<String>> playerTags = new HashMap<>();
|
||||||
// 新增:玩家-(称号-到期时间戳)映射(毫秒级,永久用Long.MAX_VALUE)
|
|
||||||
private final Map<UUID, Map<String, Long>> expireTimeMap = new HashMap<>();
|
private final Map<UUID, Map<String, Long>> expireTimeMap = new HashMap<>();
|
||||||
// 原有:玩家-选中称号索引映射
|
|
||||||
public Map<UUID, Integer> playerselectTag = new HashMap<>();
|
public Map<UUID, Integer> playerselectTag = new HashMap<>();
|
||||||
|
|
||||||
// 常量:7天的毫秒数(7*24*60*60*1000)
|
|
||||||
private static final long SEVEN_DAYS_MS = 604800000L;
|
private static final long SEVEN_DAYS_MS = 604800000L;
|
||||||
|
|
||||||
public PlayerTags(Survival plugin) {
|
public PlayerTags(Survival plugin) {
|
||||||
@@ -32,36 +28,28 @@ public class PlayerTags {
|
|||||||
this.tagsFile = new File(plugin.getDataFolder(), "playertags.data");
|
this.tagsFile = new File(plugin.getDataFolder(), "playertags.data");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 核心辅助:清理玩家的所有过期称号(所有方法调用前先执行)
|
|
||||||
*/
|
|
||||||
public void cleanExpiredTags(Player player) {
|
public void cleanExpiredTags(Player player) {
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
List<String> validTags = new ArrayList<>();
|
List<String> validTags = new ArrayList<>();
|
||||||
List<String> allTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
List<String> allTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
||||||
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
|
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
|
||||||
|
|
||||||
// 遍历所有称号,保留未过期的
|
|
||||||
for (String tag : allTags) {
|
for (String tag : allTags) {
|
||||||
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
|
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
|
||||||
// 未过期判断:永久(MAX)或当前时间 < 到期时间
|
|
||||||
if (expireTime == Long.MAX_VALUE || System.currentTimeMillis() < expireTime) {
|
if (expireTime == Long.MAX_VALUE || System.currentTimeMillis() < expireTime) {
|
||||||
validTags.add(tag);
|
validTags.add(tag);
|
||||||
} else {
|
} else {
|
||||||
// 过期:移除过期时间记录
|
|
||||||
tagExpires.remove(tag);
|
tagExpires.remove(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新有效称号列表和过期时间映射
|
|
||||||
if (validTags.isEmpty()) {
|
if (validTags.isEmpty()) {
|
||||||
playerTags.remove(uuid);
|
playerTags.remove(uuid);
|
||||||
expireTimeMap.remove(uuid);
|
expireTimeMap.remove(uuid);
|
||||||
playerselectTag.put(uuid, -1); // 无有效称号,重置选中索引
|
playerselectTag.put(uuid, -1);
|
||||||
} else {
|
} else {
|
||||||
playerTags.put(uuid, validTags);
|
playerTags.put(uuid, validTags);
|
||||||
expireTimeMap.put(uuid, tagExpires);
|
expireTimeMap.put(uuid, tagExpires);
|
||||||
// 检查选中的称号是否已过期:若过期/索引无效,重置索引
|
|
||||||
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
|
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
|
||||||
if (currentIndex == -1 || currentIndex >= validTags.size()) {
|
if (currentIndex == -1 || currentIndex >= validTags.size()) {
|
||||||
playerselectTag.put(uuid, -1);
|
playerselectTag.put(uuid, -1);
|
||||||
@@ -69,11 +57,8 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前选中的称号索引(自动过滤过期)
|
|
||||||
*/
|
|
||||||
public int getCurrentTag(Player player) {
|
public int getCurrentTag(Player player) {
|
||||||
cleanExpiredTags(player); // 先清理过期
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
Integer index = playerselectTag.get(uuid);
|
Integer index = playerselectTag.get(uuid);
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
@@ -82,34 +67,29 @@ public class PlayerTags {
|
|||||||
|
|
||||||
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
||||||
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
|
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
|
||||||
playerselectTag.put(uuid, -1); // 索引无效,重置
|
playerselectTag.put(uuid, -1);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置玩家选择的标签索引(自动过滤过期)
|
|
||||||
*/
|
|
||||||
public boolean setSelectedTag(Player player, int index) {
|
public boolean setSelectedTag(Player player, int index) {
|
||||||
cleanExpiredTags(player); // 先清理过期
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
||||||
|
|
||||||
// 无有效称号或索引无效,返回失败
|
|
||||||
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
|
if (validTags.isEmpty() || index < 0 || index >= validTags.size()) {
|
||||||
playerselectTag.put(uuid, -1);
|
playerselectTag.put(uuid, -1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置选中索引,返回成功
|
|
||||||
playerselectTag.put(uuid, index);
|
playerselectTag.put(uuid, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载称号数据:核心逻辑——识别旧数据并转为7天过期
|
* 修复核心:正确读取新版本数据中的expireTimeMap,避免误判为旧数据
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void loadTags() {
|
public void loadTags() {
|
||||||
@@ -118,17 +98,27 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(tagsFile))) {
|
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(tagsFile))) {
|
||||||
// 读取第一个对象:判断是旧版本(List<String>)还是新版本(含过期时间)
|
// 读取三个核心对象(新版本数据结构)
|
||||||
Object firstObj = ois.readObject();
|
Object firstObj = ois.readObject();
|
||||||
Object secondObj = ois.readObject();
|
Object secondObj = ois.readObject();
|
||||||
Object thirdObj = null; // 新版本的第三个对象:expireTimeMap
|
Object thirdObj = ois.readObject(); // 关键修复:读取第三个对象(expireTimeMap)
|
||||||
|
|
||||||
// 1. 识别旧版本数据(仅2个对象:playerTags + playerselectTag)
|
// 1. 优先判断新版本数据(3个对象完整)
|
||||||
if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?> && ois.available() == 0) {
|
if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?> && thirdObj instanceof Map<?, ?>) {
|
||||||
|
// 强制转换并加载数据
|
||||||
|
playerTags.putAll((Map<UUID, List<String>>) firstObj);
|
||||||
|
playerselectTag.putAll((Map<UUID, Integer>) secondObj);
|
||||||
|
expireTimeMap.putAll((Map<UUID, Map<String, Long>>) thirdObj);
|
||||||
|
plugin.getLogger().info("新版本称号数据加载完成!共加载 " + playerTags.size() + " 个玩家的称号");
|
||||||
|
return; // 加载成功直接返回,避免进入旧数据处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 处理旧版本数据(仅2个对象:无expireTimeMap)
|
||||||
|
if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?>) {
|
||||||
Map<UUID, List<String>> oldPlayerTags = (Map<UUID, List<String>>) firstObj;
|
Map<UUID, List<String>> oldPlayerTags = (Map<UUID, List<String>>) firstObj;
|
||||||
Map<UUID, Integer> oldSelectTag = (Map<UUID, Integer>) secondObj;
|
Map<UUID, Integer> oldSelectTag = (Map<UUID, Integer>) secondObj;
|
||||||
|
|
||||||
// 旧数据转换:为每个称号设置“当前时间+7天”的到期时间
|
// 旧数据转换为7天过期
|
||||||
for (Map.Entry<UUID, List<String>> entry : oldPlayerTags.entrySet()) {
|
for (Map.Entry<UUID, List<String>> entry : oldPlayerTags.entrySet()) {
|
||||||
UUID uuid = entry.getKey();
|
UUID uuid = entry.getKey();
|
||||||
List<String> oldTags = entry.getValue();
|
List<String> oldTags = entry.getValue();
|
||||||
@@ -136,34 +126,27 @@ public class PlayerTags {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为当前玩家创建“称号-到期时间”映射
|
|
||||||
Map<String, Long> tagExpires = new HashMap<>();
|
Map<String, Long> tagExpires = new HashMap<>();
|
||||||
long sevenDaysLater = System.currentTimeMillis() + SEVEN_DAYS_MS; // 7天后到期
|
long sevenDaysLater = System.currentTimeMillis() + SEVEN_DAYS_MS;
|
||||||
for (String tag : oldTags) {
|
for (String tag : oldTags) {
|
||||||
tagExpires.put(tag, sevenDaysLater); // 每个旧称号都设为7天过期
|
tagExpires.put(tag, sevenDaysLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存入新结构
|
playerTags.put(uuid, new ArrayList<>(oldTags));
|
||||||
playerTags.put(uuid, new ArrayList<>(oldTags)); // 复制旧称号列表
|
expireTimeMap.put(uuid, tagExpires);
|
||||||
expireTimeMap.put(uuid, tagExpires); // 存入7天过期时间
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复选中索引
|
|
||||||
playerselectTag.putAll(oldSelectTag);
|
playerselectTag.putAll(oldSelectTag);
|
||||||
plugin.getLogger().info("旧版本称号数据加载完成,已自动转为7日后到期!");
|
plugin.getLogger().info("旧版本称号数据加载完成,已自动转为7日后到期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
// 3. 数据格式错误
|
||||||
// 2. 识别新版本数据(3个对象:playerTags + playerselectTag + expireTimeMap)
|
plugin.getLogger().warning("称号数据格式错误,无法识别!");
|
||||||
else if (firstObj instanceof Map<?, ?> && secondObj instanceof Map<?, ?> && thirdObj instanceof Map<?, ?>) {
|
|
||||||
playerTags.putAll((Map<UUID, List<String>>) firstObj);
|
|
||||||
playerselectTag.putAll((Map<UUID, Integer>) secondObj);
|
|
||||||
expireTimeMap.putAll((Map<UUID, Map<String, Long>>) thirdObj);
|
|
||||||
plugin.getLogger().info("新版本称号数据加载完成!");
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
// 回退逻辑:仅加载称号列表,所有称号设为7天过期
|
// 回退逻辑:仅加载称号列表,设为7天过期
|
||||||
try (ObjectInputStream fallback = new ObjectInputStream(new FileInputStream(tagsFile))) {
|
try (ObjectInputStream fallback = new ObjectInputStream(new FileInputStream(tagsFile))) {
|
||||||
Object obj = fallback.readObject();
|
Object obj = fallback.readObject();
|
||||||
if (obj instanceof Map<?, ?> oldPlayerTags) {
|
if (obj instanceof Map<?, ?> oldPlayerTags) {
|
||||||
@@ -176,7 +159,7 @@ public class PlayerTags {
|
|||||||
for (Object tagObj : (List<?>) entry.getValue()) {
|
for (Object tagObj : (List<?>) entry.getValue()) {
|
||||||
if (tagObj instanceof String tag) {
|
if (tagObj instanceof String tag) {
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
tagExpires.put(tag, sevenDaysLater); // 回退时也设为7天过期
|
tagExpires.put(tag, sevenDaysLater);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,16 +176,13 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存称号数据:同步保存playerTags、playerselectTag、expireTimeMap
|
|
||||||
*/
|
|
||||||
public void saveTags() {
|
public void saveTags() {
|
||||||
if (!tagsFile.getParentFile().exists()) {
|
if (!tagsFile.getParentFile().exists()) {
|
||||||
tagsFile.getParentFile().mkdirs();
|
tagsFile.getParentFile().mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(tagsFile))) {
|
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(tagsFile))) {
|
||||||
// 按顺序写入3个核心结构(新版本)
|
// 按顺序写入三个核心结构(与加载时对应)
|
||||||
oos.writeObject(playerTags);
|
oos.writeObject(playerTags);
|
||||||
oos.writeObject(playerselectTag);
|
oos.writeObject(playerselectTag);
|
||||||
oos.writeObject(expireTimeMap);
|
oos.writeObject(expireTimeMap);
|
||||||
@@ -211,71 +191,50 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取玩家的有效称号列表(自动过滤过期)
|
|
||||||
*/
|
|
||||||
public List<String> getTags(Player player) {
|
public List<String> getTags(Player player) {
|
||||||
cleanExpiredTags(player);
|
cleanExpiredTags(player);
|
||||||
return playerTags.getOrDefault(player.getUniqueId(), new ArrayList<>());
|
return playerTags.getOrDefault(player.getUniqueId(), new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增称号:默认永久(可手动指定过期时间)
|
|
||||||
*
|
|
||||||
* @param player 目标玩家
|
|
||||||
* @param tag 称号名称
|
|
||||||
* @param isPermanent 是否永久(true=永久,false=7天过期)
|
|
||||||
*/
|
|
||||||
public void addTag(Player player, String tag, boolean isPermanent) {
|
public void addTag(Player player, String tag, boolean isPermanent) {
|
||||||
cleanExpiredTags(player); // 先清理过期
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
|
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
|
||||||
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
|
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
|
||||||
|
|
||||||
// 避免重复添加相同称号
|
|
||||||
if (!tags.contains(tag)) {
|
if (!tags.contains(tag)) {
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
// 设置过期时间:永久=Long.MAX_VALUE,临时=当前时间+7天
|
|
||||||
long expireTime = isPermanent ? Long.MAX_VALUE : System.currentTimeMillis() + SEVEN_DAYS_MS;
|
long expireTime = isPermanent ? Long.MAX_VALUE : System.currentTimeMillis() + SEVEN_DAYS_MS;
|
||||||
tagExpires.put(tag, expireTime);
|
tagExpires.put(tag, expireTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重载原有addTag方法:默认添加永久称号(保持向下兼容)
|
|
||||||
*/
|
|
||||||
public void addTag(Player player, String tag, int days) {
|
public void addTag(Player player, String tag, int days) {
|
||||||
cleanExpiredTags(player); // 先清理过期
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
|
List<String> tags = playerTags.computeIfAbsent(uuid, k -> new ArrayList<>());
|
||||||
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
|
Map<String, Long> tagExpires = expireTimeMap.computeIfAbsent(uuid, k -> new HashMap<>());
|
||||||
|
|
||||||
// 避免重复添加相同称号
|
|
||||||
if (!tags.contains(tag)) {
|
if (!tags.contains(tag)) {
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
long expireTime;
|
long expireTime;
|
||||||
if (days == 999) {
|
if (days == 999) {
|
||||||
expireTime = Long.MAX_VALUE; // 999天视为永久
|
expireTime = Long.MAX_VALUE;
|
||||||
} else if (days <= 0) {
|
} else if (days <= 0) {
|
||||||
expireTime = System.currentTimeMillis() - 1; // 天数≤0:立即过期(触发清理)
|
expireTime = System.currentTimeMillis() - 1;
|
||||||
} else {
|
} else {
|
||||||
// 计算有效期:当前时间 + 天数×24×60×60×1000(转换为毫秒)
|
|
||||||
expireTime = System.currentTimeMillis() + (long) days * 24 * 60 * 60 * 1000;
|
expireTime = System.currentTimeMillis() + (long) days * 24 * 60 * 60 * 1000;
|
||||||
}
|
}
|
||||||
tagExpires.put(tag, expireTime);
|
tagExpires.put(tag, expireTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除称号:同时删除过期时间记录
|
|
||||||
*/
|
|
||||||
public void removeTag(Player player, String tag) {
|
public void removeTag(Player player, String tag) {
|
||||||
cleanExpiredTags(player);
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
List<String> tags = playerTags.get(uuid);
|
List<String> tags = playerTags.get(uuid);
|
||||||
Map<String, Long> tagExpires = expireTimeMap.get(uuid);
|
Map<String, Long> tagExpires = expireTimeMap.get(uuid);
|
||||||
|
|
||||||
// 移除称号列表和过期时间映射中的对应条目
|
|
||||||
if (tags != null) {
|
if (tags != null) {
|
||||||
tags.remove(tag);
|
tags.remove(tag);
|
||||||
if (tags.isEmpty()) {
|
if (tags.isEmpty()) {
|
||||||
@@ -293,7 +252,6 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若移除的是当前选中称号,重置索引
|
|
||||||
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
|
int currentIndex = playerselectTag.getOrDefault(uuid, -1);
|
||||||
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
||||||
if (currentIndex != -1 && (currentIndex >= validTags.size() || !validTags.contains(tag))) {
|
if (currentIndex != -1 && (currentIndex >= validTags.size() || !validTags.contains(tag))) {
|
||||||
@@ -301,30 +259,20 @@ public class PlayerTags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增:获取称号的到期时间(返回时间戳,单位毫秒)
|
|
||||||
*
|
|
||||||
* @return 永久返回-1,过期/不存在返回-2,有效返回到期时间戳
|
|
||||||
*/
|
|
||||||
public long getTagExpireTime(Player player, String tag) {
|
public long getTagExpireTime(Player player, String tag) {
|
||||||
cleanExpiredTags(player);
|
cleanExpiredTags(player);
|
||||||
UUID uuid = player.getUniqueId();
|
UUID uuid = player.getUniqueId();
|
||||||
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
|
Map<String, Long> tagExpires = expireTimeMap.getOrDefault(uuid, new HashMap<>());
|
||||||
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
List<String> validTags = playerTags.getOrDefault(uuid, new ArrayList<>());
|
||||||
|
|
||||||
// 称号不存在或已过期,返回-2
|
|
||||||
if (!validTags.contains(tag)) {
|
if (!validTags.contains(tag)) {
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
|
long expireTime = tagExpires.getOrDefault(tag, Long.MAX_VALUE);
|
||||||
// 永久称号返回-1,否则返回时间戳
|
|
||||||
return expireTime == Long.MAX_VALUE ? -1 : expireTime;
|
return expireTime == Long.MAX_VALUE ? -1 : expireTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增:获取称号的剩余时间(返回字符串,如“2天3小时”)
|
|
||||||
*/
|
|
||||||
public String getTagRemainingTime(Player player, String tag) {
|
public String getTagRemainingTime(Player player, String tag) {
|
||||||
long expireTime = getTagExpireTime(player, tag);
|
long expireTime = getTagExpireTime(player, tag);
|
||||||
if (expireTime == -1) {
|
if (expireTime == -1) {
|
||||||
@@ -334,18 +282,15 @@ public class PlayerTags {
|
|||||||
return "已过期/不存在";
|
return "已过期/不存在";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算剩余毫秒数(可能为负,需处理)
|
|
||||||
long remainingMs = expireTime - System.currentTimeMillis();
|
long remainingMs = expireTime - System.currentTimeMillis();
|
||||||
if (remainingMs <= 0) {
|
if (remainingMs <= 0) {
|
||||||
return "已过期";
|
return "已过期";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为天、时、分
|
|
||||||
long days = remainingMs / (24 * 60 * 60 * 1000);
|
long days = remainingMs / (24 * 60 * 60 * 1000);
|
||||||
long hours = (remainingMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000);
|
long hours = (remainingMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000);
|
||||||
long minutes = (remainingMs % (60 * 60 * 1000)) / (60 * 1000);
|
long minutes = (remainingMs % (60 * 60 * 1000)) / (60 * 1000);
|
||||||
|
|
||||||
// 拼接剩余时间字符串
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (days > 0) sb.append(days).append("天");
|
if (days > 0) sb.append(days).append("天");
|
||||||
if (hours > 0) sb.append(hours).append("小时");
|
if (hours > 0) sb.append(hours).append("小时");
|
||||||
|
|||||||
@@ -1,70 +1,464 @@
|
|||||||
package org.xgqy.survival;
|
package org.xgqy.survival;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.xgqy.survival.command.CommandTabExecutor;
|
||||||
import org.xgqy.survival.command.DqshopCommandExecutor;
|
import org.xgqy.survival.command.DqshopCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.FriendCommandExecutor;
|
||||||
import org.xgqy.survival.command.HandleCommandExecutor;
|
import org.xgqy.survival.command.HandleCommandExecutor;
|
||||||
import org.xgqy.survival.command.HelpCommandExecutor;
|
import org.xgqy.survival.command.HelpCommandExecutor;
|
||||||
import org.xgqy.survival.command.HubCommandExecutor;
|
import org.xgqy.survival.command.HubCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.InventoryCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.KillCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.LandCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.LoginCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.MessageBoardCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.NoticeCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.PartyCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.PcCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.PointCommandExecutor;
|
||||||
import org.xgqy.survival.command.PvpCommandExecutor;
|
import org.xgqy.survival.command.PvpCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.RankCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.RegCommandExecutor;
|
||||||
import org.xgqy.survival.command.ReportCommandExecutor;
|
import org.xgqy.survival.command.ReportCommandExecutor;
|
||||||
import org.xgqy.survival.command.SetTagCommandExecutor;
|
import org.xgqy.survival.command.SetTagCommandExecutor;
|
||||||
import org.xgqy.survival.command.TagCommandExecutor;
|
import org.xgqy.survival.command.TagCommandExecutor;
|
||||||
import org.xgqy.survival.command.TeleportCommandExecutor;
|
import org.xgqy.survival.command.TeleportCommandExecutor;
|
||||||
import org.xgqy.survival.command.TpAccCommandExecutor;
|
import org.xgqy.survival.command.TpAccCommandExecutor;
|
||||||
import org.xgqy.survival.command.TpFinCommandExecutor;
|
import org.xgqy.survival.command.TpFinCommandExecutor;
|
||||||
|
import org.xgqy.survival.command.msgCommandExecutor;
|
||||||
import org.xgqy.survival.event.ChatEvent;
|
import org.xgqy.survival.event.ChatEvent;
|
||||||
import org.xgqy.survival.event.ChooseTagEvent;
|
import org.xgqy.survival.event.ChooseTagEvent;
|
||||||
|
import org.xgqy.survival.event.ForceSurvival;
|
||||||
import org.xgqy.survival.event.JoinEvent;
|
import org.xgqy.survival.event.JoinEvent;
|
||||||
|
import org.xgqy.survival.event.LandEvent;
|
||||||
|
import org.xgqy.survival.event.LoginEvent;
|
||||||
|
import org.xgqy.survival.event.RankAdd;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public final class Survival extends JavaPlugin {
|
public final class Survival extends JavaPlugin {
|
||||||
private PlayerTags playerTags;
|
private PlayerTags playerTags;
|
||||||
|
private AccountManager accountManager;
|
||||||
public Map<Player, Boolean> krt = new HashMap<>();
|
public Map<Player, Boolean> krt = new HashMap<>();
|
||||||
public Map<Player, Player> banlist = new HashMap<>();
|
public Map<Player, Player> banlist = new HashMap<>();
|
||||||
public Map<Player, String> banreason = new HashMap<>();
|
public Map<Player, String> banreason = new HashMap<>();
|
||||||
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
|
public Map<Player, HashSet<Player>> reportlist = new HashMap<>();
|
||||||
|
// Teleport
|
||||||
|
private RankAdd contributionManager;
|
||||||
public Map<Player, Player> teleport = new HashMap<>();
|
public Map<Player, Player> teleport = new HashMap<>();
|
||||||
public Map<Player, Player> Ateleport = new HashMap<>();
|
public Map<Player, Player> Ateleport = new HashMap<>();
|
||||||
public Map<Player, Location> teleportp = new HashMap<>();
|
public Map<Player, Location> teleportp = new HashMap<>();
|
||||||
public Map<Player, Integer> isteleport = new HashMap<>();
|
public Map<Player, Integer> isteleport = new HashMap<>();
|
||||||
public Map<Player, Long> lasttp = new HashMap<>();
|
public Map<Player, Long> lasttp = new HashMap<>();
|
||||||
|
public Map<Player, Location> area = new HashMap<>();
|
||||||
|
/*
|
||||||
|
----------------------------------------
|
||||||
|
party
|
||||||
|
----------------------------------------
|
||||||
|
*/
|
||||||
|
public Map<Player, Integer> party = new HashMap<>();
|
||||||
|
public Map<Integer, List<Player>> partyp = new HashMap<>();
|
||||||
|
public Map<Integer, Player> owner = new HashMap<>();
|
||||||
|
public Map<Integer, List<Player>> ban = new HashMap<>();
|
||||||
|
/*
|
||||||
|
----------------------------------------
|
||||||
|
friend
|
||||||
|
----------------------------------------
|
||||||
|
*/
|
||||||
|
public Map<Player, List<Player>> friends = new ConcurrentHashMap<>();
|
||||||
|
public Map<Player, List<Player>> waiting = new ConcurrentHashMap<>();
|
||||||
|
/*
|
||||||
|
----------------------------------------
|
||||||
|
信用分
|
||||||
|
----------------------------------------
|
||||||
|
*/
|
||||||
|
public Map<Player, Integer> ppoint = new HashMap<>();
|
||||||
|
private List<String> msg = new ArrayList<>();
|
||||||
|
public Map<Player, String> breason = new HashMap<>();
|
||||||
|
/*
|
||||||
|
----------------------------------------
|
||||||
|
领地
|
||||||
|
----------------------------------------
|
||||||
|
*/
|
||||||
|
public static class Land{
|
||||||
|
public Location start,end;
|
||||||
|
public List<Boolean> defaultperm;
|
||||||
|
public String owner;
|
||||||
|
public Map<String,List<Boolean>> perm;
|
||||||
|
public boolean boomb;
|
||||||
|
public Land(Location start,Location end,List<Boolean> defaultperm,String owner,Map<String,List<Boolean>> perm){
|
||||||
|
this.start=start;
|
||||||
|
this.end=end;
|
||||||
|
this.defaultperm = defaultperm;
|
||||||
|
this.owner=owner;
|
||||||
|
this.perm=perm;
|
||||||
|
}
|
||||||
|
public Boolean getBoomb(){
|
||||||
|
return boomb;
|
||||||
|
}
|
||||||
|
public void setBoomb(Boolean boomb){
|
||||||
|
this.boomb=boomb;
|
||||||
|
}
|
||||||
|
public Location getStart(){
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
public Location getEnd(){
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
public List<Boolean> getDefaultperm(){
|
||||||
|
return defaultperm;
|
||||||
|
}
|
||||||
|
public String getOwner(){
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
public List<Boolean> getPerm(Player player){
|
||||||
|
return perm.getOrDefault(player,null);
|
||||||
|
}
|
||||||
|
public void setStart(Location start){
|
||||||
|
this.start=start;
|
||||||
|
}
|
||||||
|
public void setEnd(Location end) {
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
public void setDefaultperm(List<Boolean> defaultperm){
|
||||||
|
this.defaultperm=defaultperm;
|
||||||
|
}
|
||||||
|
public void setOwner(String owner){
|
||||||
|
this.owner=owner;
|
||||||
|
}
|
||||||
|
public void setPerm(Player player,List<Boolean> perm){
|
||||||
|
this.perm.put(player.getName(),perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public List<Land> land =new ArrayList<>();
|
||||||
|
|
||||||
|
public Map<Player,String> inland=new HashMap<>();
|
||||||
|
// 信用分数据文件相关
|
||||||
|
private File ppointFile;
|
||||||
|
private FileConfiguration ppointConfig;
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Plugin startup logic
|
// 初始化账号管理器
|
||||||
|
accountManager = new AccountManager(this);
|
||||||
|
|
||||||
|
// 初始化信用分数据文件
|
||||||
|
initPpointFile();
|
||||||
|
// 加载已在线玩家的信用分数据
|
||||||
|
loadPpoint();
|
||||||
|
|
||||||
|
msg.add(ChatColor.GREEN + "可以用 /help 命令来查看命令列表");
|
||||||
|
msg.add(ChatColor.GREEN + "如果遇到作弊玩家可以用 /report <玩家名> <原因> 来举报!管理员会很快处理的");
|
||||||
|
msg.add(ChatColor.GREEN + "想传送到玩家怎么办? 用 /teleport <玩家名> 命令!");
|
||||||
|
msg.add(ChatColor.GREEN + "更换IP但没有更换账号导致的封禁不能找管理员解决");
|
||||||
|
msg.add(ChatColor.GREEN + "星阁钱语的名称其实叫星阁浅语");
|
||||||
|
msg.add(ChatColor.RED + "作弊会导致账号封禁!请不要作弊");
|
||||||
|
msg.add(ChatColor.RED + "在服务器内说不文明用语会导致账号被封禁,请不要这样做");
|
||||||
|
msg.add(ChatColor.GREEN + "今天签到了吗?快输入 /qd 签到吧!");
|
||||||
|
msg.add(ChatColor.GREEN + "其实可以输入 /pvp 来开启/关闭自己的pvp模式");
|
||||||
|
msg.add(ChatColor.RED + "你不可以把别人的家毁掉!这样你将会受到处罚");
|
||||||
|
msg.add(ChatColor.GREEN + "每日坚持签到可以获得信用分");
|
||||||
|
msg.add(ChatColor.GREEN + "服务器官网: https://starpavilion.xyz");
|
||||||
|
msg.add(ChatColor.GREEN + "赞助我们? https://starpavilion.xyz/sponsor.html");
|
||||||
|
msg.add(ChatColor.RED+"封禁者会在此处公示: https://starpavilion.xyz/bans.html");
|
||||||
|
msg.add(ChatColor.RED + "信用分过低将会导致你的账号受限甚至永久封禁!");
|
||||||
|
msg.add(ChatColor.GREEN+"小小赞助一下吧~");
|
||||||
|
msg.add(ChatColor.GREEN + "信用分被扣除会提醒,如果您的信用分被莫名扣除,请及时到QQ群 717903781 申诉。");
|
||||||
|
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (Player plr : Bukkit.getOnlinePlayers()) {
|
||||||
|
// 只向已登录玩家发送提示
|
||||||
|
if (accountManager.isLoggedIn(plr)) {
|
||||||
|
plr.sendMessage(ChatColor.LIGHT_PURPLE + "小提示 " + ChatColor.WHITE + " | " + msg.get((int) (Math.random() * 10000) % msg.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskLater(this, 20 * 60);
|
||||||
|
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (Player plr : Bukkit.getOnlinePlayers()) {
|
||||||
|
// 检查玩家是否有违规扣分记录
|
||||||
|
if (plr.getScoreboard().getObjective("handled") != null &&
|
||||||
|
plr.getScoreboard().getObjective("handled").getScore(plr).getScore() != 0) {
|
||||||
|
|
||||||
|
ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
String formattedTime = beijingTime.format(formatter);
|
||||||
|
|
||||||
|
// 确保玩家信用分已初始化
|
||||||
|
int currentPoint = ppoint.getOrDefault(plr, 100);
|
||||||
|
int deductPoint = plr.getScoreboard().getObjective("handled").getScore(plr).getScore();
|
||||||
|
int newPoint = currentPoint - deductPoint;
|
||||||
|
//ppoint.put(plr, newPoint);
|
||||||
|
|
||||||
|
// 发送扣分提示
|
||||||
|
plr.sendMessage(ChatColor.RED + "警告" + ChatColor.WHITE + " | " + ChatColor.RED + "您的账号 " + ChatColor.YELLOW + plr.getName() + ChatColor.RED + " 在 " + ChatColor.WHITE + formattedTime + ChatColor.RED + " 有疑似违规行为,扣取信用分 " + ChatColor.YELLOW + deductPoint + ChatColor.RED + " 分。\n");
|
||||||
|
plr.sendMessage(ChatColor.RED + "-----------警告单-----------");
|
||||||
|
plr.sendMessage(ChatColor.RED + "|账号名: " + ChatColor.YELLOW + plr.getName());
|
||||||
|
plr.sendMessage(ChatColor.RED + "|违规时间: " + ChatColor.YELLOW + formattedTime);
|
||||||
|
plr.sendMessage(ChatColor.RED + "|违规行为: " + ChatColor.YELLOW + breason.getOrDefault(plr, "管理员扣分"));
|
||||||
|
plr.sendMessage(ChatColor.RED + "|违规扣分: " + ChatColor.YELLOW + deductPoint + " 分");
|
||||||
|
plr.sendMessage(ChatColor.RED + "|剩余信用分: " + ChatColor.YELLOW + newPoint);
|
||||||
|
|
||||||
|
// 根据剩余信用分执行处罚
|
||||||
|
if (newPoint > 80) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "无");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(0);
|
||||||
|
} else if(newPoint <= 0){
|
||||||
|
plr.kickPlayer(ChatColor.RED + "您好! 您由于在 " + formattedTime + " 信用分小于 0 ,我们决定对你的账号 " + plr.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
||||||
|
}else if (newPoint <= 10) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(4);
|
||||||
|
} else if (newPoint <= 30) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(3);
|
||||||
|
} else if (newPoint <= 60) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,限制功能使用");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(2);
|
||||||
|
} else if (newPoint <= 80) {
|
||||||
|
plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "监管行为及发言,签到将奖励转换为信用分");
|
||||||
|
plr.getScoreboard().getObjective("check").getScore(plr).setScore(1);
|
||||||
|
}
|
||||||
|
plr.sendMessage(ChatColor.RED + "|为了维护游戏环境,请不要违规!");
|
||||||
|
plr.sendMessage(ChatColor.RED + "----------------------------");
|
||||||
|
|
||||||
|
// 清理临时数据
|
||||||
|
breason.remove(plr);
|
||||||
|
plr.getScoreboard().getObjective("handled").getScore(plr).setScore(0);
|
||||||
|
|
||||||
|
// 实时保存扣分后的信用分数据
|
||||||
|
savePpoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(this, 20 * 5, 20 * 5);
|
||||||
|
|
||||||
|
// 初始化玩家标签
|
||||||
playerTags = new PlayerTags(this);
|
playerTags = new PlayerTags(this);
|
||||||
playerTags.loadTags();
|
playerTags.loadTags();
|
||||||
|
|
||||||
|
// 注册事件监听器
|
||||||
Bukkit.getPluginManager().registerEvents(new JoinEvent(this), this);
|
Bukkit.getPluginManager().registerEvents(new JoinEvent(this), this);
|
||||||
Bukkit.getPluginManager().registerEvents(new ChatEvent(this), this);
|
Bukkit.getPluginManager().registerEvents(new ChatEvent(this), this);
|
||||||
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
|
Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this);
|
||||||
//Bukkit.getPluginManager().registerEvents(new AntiXray(this),this);
|
Bukkit.getPluginManager().registerEvents(new LoginEvent(this), this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(new LandEvent(this),this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(new ForceSurvival(),this);
|
||||||
|
|
||||||
|
// 注册命令执行器
|
||||||
getCommand("report").setExecutor(new ReportCommandExecutor(this));
|
getCommand("report").setExecutor(new ReportCommandExecutor(this));
|
||||||
getCommand("handle").setExecutor(new HandleCommandExecutor(this));
|
getCommand("handle").setExecutor(new HandleCommandExecutor(this));
|
||||||
getCommand("pvp").setExecutor(new PvpCommandExecutor(this));
|
getCommand("pvp").setExecutor(new PvpCommandExecutor(this));
|
||||||
getCommand("settag").setExecutor(new SetTagCommandExecutor(this));
|
getCommand("settag").setExecutor(new SetTagCommandExecutor(this));
|
||||||
getCommand("help").setExecutor(new HelpCommandExecutor(this));
|
getCommand("help").setExecutor(new HelpCommandExecutor(this));
|
||||||
//getCommand("fly").setExecutor(new FlyCommandExecutor(this));
|
|
||||||
getCommand("tag").setExecutor(new TagCommandExecutor(this));
|
getCommand("tag").setExecutor(new TagCommandExecutor(this));
|
||||||
getCommand("hub").setExecutor(new HubCommandExecutor(this));
|
getCommand("hub").setExecutor(new HubCommandExecutor(this));
|
||||||
getCommand("teleport").setExecutor(new TeleportCommandExecutor(this));
|
getCommand("teleport").setExecutor(new TeleportCommandExecutor(this));
|
||||||
getCommand("tpacc").setExecutor(new TpAccCommandExecutor(this));
|
getCommand("tpacc").setExecutor(new TpAccCommandExecutor(this));
|
||||||
getCommand("tpfin").setExecutor(new TpFinCommandExecutor(this));
|
getCommand("tpfin").setExecutor(new TpFinCommandExecutor(this));
|
||||||
getCommand("shop").setExecutor(new DqshopCommandExecutor(this));
|
getCommand("shop").setExecutor(new DqshopCommandExecutor(this));
|
||||||
}
|
getCommand("reg").setExecutor(new RegCommandExecutor(this));
|
||||||
|
getCommand("login").setExecutor(new LoginCommandExecutor(this));
|
||||||
|
getCommand("party").setExecutor(new PartyCommandExecutor(this));
|
||||||
|
getCommand("inventory").setExecutor(new InventoryCommandExecutor(this));
|
||||||
|
getCommand("pc").setExecutor(new PcCommandExecutor(this));
|
||||||
|
getCommand("msg").setExecutor(new msgCommandExecutor(this));
|
||||||
|
getCommand("friend").setExecutor(new FriendCommandExecutor(this));
|
||||||
|
getCommand("point").setExecutor(new PointCommandExecutor(this));
|
||||||
|
getCommand("home").setExecutor(new KillCommandExecutor(this));
|
||||||
|
getCommand("land").setExecutor(new LandCommandExecutor(this));
|
||||||
|
getCommand("qd").setExecutor(new LandCommandExecutor(this));
|
||||||
|
getCommand("notice").setExecutor(new NoticeCommandExecutor(this));
|
||||||
|
MessageBoardCommandExecutor mbExecutor = new MessageBoardCommandExecutor(this);
|
||||||
|
this.getCommand("messageboard").setExecutor(mbExecutor);
|
||||||
|
this.getCommand("mb").setExecutor(mbExecutor); // 别名支持
|
||||||
|
this.contributionManager = new RankAdd(this);
|
||||||
|
|
||||||
|
// 注册排行榜命令
|
||||||
|
RankCommandExecutor rankExecutor = new RankCommandExecutor(this);
|
||||||
|
this.getCommand("rank").setExecutor(rankExecutor);
|
||||||
|
CommandTabExecutor tabExecutor = new CommandTabExecutor();
|
||||||
|
registerTabCompleter(tabExecutor);
|
||||||
|
}
|
||||||
|
public RankAdd getContributionManager() {
|
||||||
|
return contributionManager;
|
||||||
|
}
|
||||||
|
private void registerTabCompleter(CommandTabExecutor completer) {
|
||||||
|
// 所有需要补全的命令列表
|
||||||
|
List<String> commands = Arrays.asList(
|
||||||
|
"handle", "tpacc", "friend", "party", "settag", "teleport",
|
||||||
|
"point", "land", "pvp", "help", "tag", "hub", "home",
|
||||||
|
"qd", "reg", "login", "msg", "inventory", "notice","messageboard","mb",
|
||||||
|
"rank"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (String cmd : commands) {
|
||||||
|
if (getCommand(cmd) != null) {
|
||||||
|
getCommand(cmd).setTabCompleter(completer);
|
||||||
|
} else {
|
||||||
|
getLogger().warning("命令 " + cmd + " 未在 plugin.yml 中配置,无法注册补全器!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
// 保存玩家标签数据
|
||||||
playerTags.saveTags();
|
playerTags.saveTags();
|
||||||
// Plugin shutdown logic
|
for(Player p : Bukkit.getOnlinePlayers()){
|
||||||
|
p.kickPlayer(ChatColor.GREEN+"========================================================\n"+
|
||||||
|
ChatColor.RED+"服务器重启,请稍等 1 - 3 分钟后再次加入\n"+
|
||||||
|
ChatColor.GREEN+"========================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存信用分数据
|
||||||
|
savePpoint();
|
||||||
|
|
||||||
|
// 保存好友数据
|
||||||
|
try {
|
||||||
|
FriendCommandExecutor executor = (FriendCommandExecutor) this.getCommand("friend").getExecutor();
|
||||||
|
if (executor != null) {
|
||||||
|
getLogger().info("好友数据已保存!");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().severe("插件卸载时保存好友数据失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存账号信息
|
||||||
|
accountManager.saveAccounts();
|
||||||
|
|
||||||
|
getLogger().info("Survival插件已禁用,所有数据已保存!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化信用分数据文件
|
||||||
|
private void initPpointFile() {
|
||||||
|
// 创建插件数据文件夹(若不存在)
|
||||||
|
File dataFolder = getDataFolder();
|
||||||
|
if (!dataFolder.exists()) {
|
||||||
|
if (dataFolder.mkdirs()) {
|
||||||
|
getLogger().info("插件数据文件夹创建成功!");
|
||||||
|
} else {
|
||||||
|
getLogger().severe("插件数据文件夹创建失败!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建信用分文件(路径:plugins/Survival/ppoint.yml)
|
||||||
|
ppointFile = new File(dataFolder, "ppoint.yml");
|
||||||
|
if (!ppointFile.exists()) {
|
||||||
|
try {
|
||||||
|
if (ppointFile.createNewFile()) {
|
||||||
|
getLogger().info("信用分数据文件创建成功!");
|
||||||
|
} else {
|
||||||
|
getLogger().severe("信用分数据文件创建失败!");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().severe("创建信用分文件时发生异常:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置文件
|
||||||
|
ppointConfig = YamlConfiguration.loadConfiguration(ppointFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存信用分数据到文件(关键修正处)
|
||||||
|
public void savePpoint() {
|
||||||
|
if (ppointConfig == null || ppointFile == null) {
|
||||||
|
getLogger().severe("信用分配置未初始化,保存失败!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修正:清空原有配置的正确方式(将所有键设为null或移除)
|
||||||
|
for (String key : ppointConfig.getKeys(false)) {
|
||||||
|
ppointConfig.set(key, null); // 用null覆盖原有值,或用 ppointConfig.remove(key) 直接移除键
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将在线玩家的信用分存入配置(用UUID作为键,参数完整)
|
||||||
|
for (Map.Entry<Player, Integer> entry : ppoint.entrySet()) {
|
||||||
|
Player player = entry.getKey();
|
||||||
|
if (player.isOnline()) {
|
||||||
|
UUID playerUuid = player.getUniqueId();
|
||||||
|
ppointConfig.set(playerUuid.toString(), entry.getValue()); // 完整参数:路径(UUID字符串)+ 值(信用分)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
try {
|
||||||
|
ppointConfig.save(ppointFile);
|
||||||
|
getLogger().info("信用分数据保存成功!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().severe("保存信用分数据失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载信用分数据(仅加载在线玩家)
|
||||||
|
private void loadPpoint() {
|
||||||
|
if (ppointConfig == null || ppointFile == null) {
|
||||||
|
getLogger().severe("信用分配置未初始化,加载失败!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历配置中的所有UUID,为在线玩家分配信用分
|
||||||
|
for (String uuidStr : ppointConfig.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID playerUuid = UUID.fromString(uuidStr);
|
||||||
|
Player player = Bukkit.getPlayer(playerUuid);
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
// 读取信用分(默认100分)
|
||||||
|
int point = ppointConfig.getInt(uuidStr, 100);
|
||||||
|
ppoint.put(player, point);
|
||||||
|
getLogger().info("加载玩家 " + player.getName() + " 的信用分:" + point);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
getLogger().warning("无效的UUID格式:" + uuidStr + ",跳过该数据");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 玩家上线时加载其信用分(供JoinEvent/LoginEvent调用)
|
||||||
|
public void loadPlayerPpoint(Player player) {
|
||||||
|
if (ppointConfig == null) {
|
||||||
|
getLogger().severe("信用分配置未初始化,无法加载玩家 " + player.getName() + " 的信用分!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID playerUuid = player.getUniqueId();
|
||||||
|
String uuidStr = playerUuid.toString();
|
||||||
|
|
||||||
|
// 读取已有信用分,无数据则设为默认100分
|
||||||
|
int point = ppointConfig.getInt(uuidStr, 100);
|
||||||
|
ppoint.put(player, point);
|
||||||
|
getLogger().info("玩家 " + player.getName() + " 上线,加载信用分:" + point);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取玩家标签管理器
|
||||||
public PlayerTags getPlayerTags() {
|
public PlayerTags getPlayerTags() {
|
||||||
return playerTags;
|
return playerTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取账号管理器
|
||||||
|
public AccountManager getAccountManager() {
|
||||||
|
return accountManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
260
src/main/java/org/xgqy/survival/command/CommandTabExecutor.java
Normal file
260
src/main/java/org/xgqy/survival/command/CommandTabExecutor.java
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
public class CommandTabExecutor implements TabCompleter {
|
||||||
|
private static final Map<String, String> COLOR_MAP = new HashMap<>();
|
||||||
|
static {
|
||||||
|
COLOR_MAP.put("black", "");
|
||||||
|
COLOR_MAP.put("dark_blue", "");
|
||||||
|
COLOR_MAP.put("dark_green", "");
|
||||||
|
COLOR_MAP.put("dark_aqua", "");
|
||||||
|
COLOR_MAP.put("dark_red", "");
|
||||||
|
COLOR_MAP.put("dark_purple", "");
|
||||||
|
COLOR_MAP.put("gold", "");
|
||||||
|
COLOR_MAP.put("gray", "");
|
||||||
|
COLOR_MAP.put("dark_gray", "");
|
||||||
|
COLOR_MAP.put("blue", "");
|
||||||
|
COLOR_MAP.put("green", "");
|
||||||
|
COLOR_MAP.put("aqua", "");
|
||||||
|
COLOR_MAP.put("red", "");
|
||||||
|
COLOR_MAP.put("light_purple", "");
|
||||||
|
COLOR_MAP.put("yellow", "");
|
||||||
|
COLOR_MAP.put("white", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
String cmdName = command.getName().toLowerCase();
|
||||||
|
switch (cmdName) {
|
||||||
|
case "handle":
|
||||||
|
completions = handleTabComplete(args);
|
||||||
|
break;
|
||||||
|
case "tpacc":
|
||||||
|
completions = tpAccTabComplete(args);
|
||||||
|
break;
|
||||||
|
case "friend":
|
||||||
|
completions = friendTabComplete(sender, args);
|
||||||
|
break;
|
||||||
|
case "party":
|
||||||
|
completions = partyTabComplete(sender, args);
|
||||||
|
break;
|
||||||
|
case "settag":
|
||||||
|
completions = setTagTabComplete(sender, args);
|
||||||
|
break;
|
||||||
|
case "teleport":
|
||||||
|
completions = teleportTabComplete(sender, args);
|
||||||
|
break;
|
||||||
|
case "point":
|
||||||
|
case "land":
|
||||||
|
completions = pointLandTabComplete(sender, args);
|
||||||
|
break;
|
||||||
|
case "pvp":
|
||||||
|
case "help":
|
||||||
|
case "tag":
|
||||||
|
case "hub":
|
||||||
|
case "home":
|
||||||
|
case "qd":
|
||||||
|
case "reg":
|
||||||
|
case "login":
|
||||||
|
case "msg":
|
||||||
|
case "inventory":
|
||||||
|
case "notice":
|
||||||
|
completions = simpleCommandTabComplete(sender, cmdName, args);
|
||||||
|
break;
|
||||||
|
case "mb":
|
||||||
|
case "messageboard":
|
||||||
|
completions = mbTabCompelete(args);
|
||||||
|
case "rank":
|
||||||
|
completions = rankTabCompelete((Player) sender,args);
|
||||||
|
default:
|
||||||
|
completions.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> rankTabCompelete(Player sender,String[] args){
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if(sender.isOp()){
|
||||||
|
if(args.length == 1){
|
||||||
|
List<String> options = Arrays.asList("add","remove","all");
|
||||||
|
completions = filterMatches(options,args[0]);
|
||||||
|
}
|
||||||
|
else if(args.length == 2 && (args[1] == "add" || args[1] == "remove")){
|
||||||
|
completions.add("<数值>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> mbTabCompelete(String[] args){
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if(args.length == 1){
|
||||||
|
List<String> options = Arrays.asList("add","list","support");
|
||||||
|
completions=filterMatches(options,args[0]);
|
||||||
|
}else{
|
||||||
|
completions.add("<内容>");
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> handleTabComplete(String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (args.length == 1) {
|
||||||
|
List<String> options = Arrays.asList(
|
||||||
|
"no", "point", "12h", "1d", "3d", "7d", "14d",
|
||||||
|
"1mo", "3mo", "1y", "ban"
|
||||||
|
);
|
||||||
|
completions = filterMatches(options, args[0]);
|
||||||
|
}
|
||||||
|
else if (args.length == 2 && "ban".equalsIgnoreCase(args[0])) {
|
||||||
|
completions.add("<天数>");
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> tpAccTabComplete(String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (args.length == 1) {
|
||||||
|
List<String> options = Arrays.asList("accept", "deny", "tome", "detome");
|
||||||
|
completions = filterMatches(options, args[0]);
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> friendTabComplete(CommandSender sender, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
if (args.length == 1) {
|
||||||
|
List<String> options = Arrays.asList("add", "allow", "deny", "remove", "list");
|
||||||
|
completions = filterMatches(options, args[0]);
|
||||||
|
}
|
||||||
|
else if (args.length == 2 && Arrays.asList("add", "allow", "deny", "remove").contains(args[0].toLowerCase())) {
|
||||||
|
completions = getOnlinePlayers(args[1]);
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> partyTabComplete(CommandSender sender, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
if (args.length == 1) {
|
||||||
|
List<String> options = Arrays.asList(
|
||||||
|
"create", "quit", "join", "list", "kick", "ban", "disband", "help"
|
||||||
|
);
|
||||||
|
completions = filterMatches(options, args[0]);
|
||||||
|
}
|
||||||
|
else if (args.length == 2 && Arrays.asList("join", "kick", "ban").contains(args[0].toLowerCase())) {
|
||||||
|
completions = getOnlinePlayers(args[1]);
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> setTagTabComplete(CommandSender sender, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!sender.hasPermission("permission.settag")) return completions;
|
||||||
|
if (args.length == 1) {
|
||||||
|
completions = getOnlinePlayers(args[1]);
|
||||||
|
}
|
||||||
|
else if (args.length == 2) {
|
||||||
|
List<String> options = Arrays.asList("add", "remove", "check");
|
||||||
|
completions = filterMatches(options, args[1]);
|
||||||
|
}
|
||||||
|
else if (args.length == 3) {
|
||||||
|
String op = args[1].toLowerCase();
|
||||||
|
if (Arrays.asList("add", "check", "remove").contains(op)) {
|
||||||
|
completions.add("<标签内容>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.length == 4) {
|
||||||
|
String op = args[1].toLowerCase();
|
||||||
|
if (Arrays.asList("add", "check").contains(op)) {
|
||||||
|
completions = filterMatches(new ArrayList<>(COLOR_MAP.keySet()), args[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.length == 5 && "add".equalsIgnoreCase(args[1])) {
|
||||||
|
completions.add("<天数(999=永久)>");
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> teleportTabComplete(CommandSender sender, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
|
||||||
|
// 一级参数:在线玩家名
|
||||||
|
if (args.length == 1) {
|
||||||
|
completions = getOnlinePlayers(args[0]);
|
||||||
|
}
|
||||||
|
// 二级参数:me
|
||||||
|
else if (args.length == 2) {
|
||||||
|
if ("me".startsWith(args[1].toLowerCase())) {
|
||||||
|
completions.add("me");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> pointLandTabComplete(CommandSender sender, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
if (args.length == 1) {
|
||||||
|
completions = getOnlinePlayers(args[0]);
|
||||||
|
}
|
||||||
|
else if (args.length == 2) {
|
||||||
|
List<String> options = Arrays.asList("set", "add", "remove");
|
||||||
|
completions = filterMatches(options, args[1]);
|
||||||
|
}
|
||||||
|
else if (args.length == 3) {
|
||||||
|
completions.add("<数值>");
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private List<String> simpleCommandTabComplete(CommandSender sender, String cmdName, String[] args) {
|
||||||
|
List<String> completions = new ArrayList<>();
|
||||||
|
if (!(sender instanceof Player)) return completions;
|
||||||
|
switch (cmdName) {
|
||||||
|
case "reg":
|
||||||
|
if (args.length == 1) completions.add("<密码>");
|
||||||
|
else if (args.length == 2) completions.add("<确认密码>");
|
||||||
|
break;
|
||||||
|
case "login":
|
||||||
|
if (args.length == 1) completions.add("<密码>");
|
||||||
|
break;
|
||||||
|
case "msg":
|
||||||
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
|
else if (args.length == 2) completions.add("<消息内容>");
|
||||||
|
break;
|
||||||
|
case "inventory":
|
||||||
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
|
break;
|
||||||
|
case "notice":
|
||||||
|
if (args.length == 1) completions = getOnlinePlayers(args[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
completions.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
private static List<String> getOnlinePlayers(String partialName) {
|
||||||
|
List<String> playerNames = new ArrayList<>();
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
playerNames.add(player.getName());
|
||||||
|
}
|
||||||
|
return filterMatches(playerNames, partialName);
|
||||||
|
}
|
||||||
|
private static List<String> filterMatches(List<String> candidates, String partial) {
|
||||||
|
String lowerPartial = partial.toLowerCase();
|
||||||
|
return candidates.stream()
|
||||||
|
.filter(candidate -> candidate.toLowerCase().startsWith(lowerPartial))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
package org.xgqy.survival.command;
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.inventory.Inventory;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DqshopCommandExecutor implements CommandExecutor {
|
public class DqshopCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
private Survival plugin;
|
private Survival plugin;
|
||||||
@@ -26,8 +17,10 @@ public class DqshopCommandExecutor implements CommandExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
if (sender instanceof Player) {
|
//if (sender instanceof Player) {
|
||||||
Inventory shopGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "商城");
|
sender.sendMessage(ChatColor.RED+"本功能已废弃!请前往官网 https://starpavilion.xyz");
|
||||||
|
return true;
|
||||||
|
/*Inventory shopGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "商城");
|
||||||
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||||
ItemMeta headmeta = head.getItemMeta();
|
ItemMeta headmeta = head.getItemMeta();
|
||||||
List<String> meta1 = new ArrayList<>();
|
List<String> meta1 = new ArrayList<>();
|
||||||
@@ -181,6 +174,6 @@ public class DqshopCommandExecutor implements CommandExecutor {
|
|||||||
sender.sendMessage(ChatColor.RED + "仅玩家能使用该命令!");
|
sender.sendMessage(ChatColor.RED + "仅玩家能使用该命令!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,586 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class FriendCommandExecutor implements CommandExecutor {
|
||||||
|
// 命令前缀
|
||||||
|
private final String prefix = ChatColor.LIGHT_PURPLE + "好友系统 " + ChatColor.WHITE + "| ";
|
||||||
|
// 好友关系存储(双向绑定)
|
||||||
|
private Map<Player, List<Player>> friends;
|
||||||
|
// 待处理好友请求(key:接收者,value:请求列表)
|
||||||
|
private Map<Player, List<FriendRequest>> waitingRequests;
|
||||||
|
// Gson实例(JSON序列化)
|
||||||
|
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
// 数据文件
|
||||||
|
private final File dataFile;
|
||||||
|
// 插件实例
|
||||||
|
private final Survival plugin;
|
||||||
|
// 请求有效期(5分钟 = 300000毫秒)
|
||||||
|
private static final long REQUEST_EXPIRE_TIME = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
public FriendCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// 初始化数据文件
|
||||||
|
this.dataFile = new File(plugin.getDataFolder(), "friend.json");
|
||||||
|
// 确保数据文件夹存在
|
||||||
|
if (!plugin.getDataFolder().exists()) {
|
||||||
|
plugin.getDataFolder().mkdirs();
|
||||||
|
}
|
||||||
|
// 加载历史数据
|
||||||
|
loadData();
|
||||||
|
// 启动请求过期清理任务
|
||||||
|
startExpireCheckTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友请求实体(存储发送者UUID和时间戳,支持序列化)
|
||||||
|
*/
|
||||||
|
private static class FriendRequest {
|
||||||
|
private final String senderUuid;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
public FriendRequest(Player sender) {
|
||||||
|
this.senderUuid = sender.getUniqueId().toString();
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter
|
||||||
|
public String getSenderUuid() {
|
||||||
|
return senderUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON序列化辅助类
|
||||||
|
*/
|
||||||
|
private static class FriendData {
|
||||||
|
private Map<String, List<String>> friends;
|
||||||
|
private Map<String, List<FriendRequest>> waitingRequests;
|
||||||
|
|
||||||
|
// 必须保留默认构造函数(Gson反序列化用)
|
||||||
|
public FriendData() {}
|
||||||
|
|
||||||
|
public FriendData(Map<String, List<String>> friends, Map<String, List<FriendRequest>> waitingRequests) {
|
||||||
|
this.friends = friends;
|
||||||
|
this.waitingRequests = waitingRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter & Setter
|
||||||
|
public Map<String, List<String>> getFriends() {
|
||||||
|
return friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFriends(Map<String, List<String>> friends) {
|
||||||
|
this.friends = friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<FriendRequest>> getWaitingRequests() {
|
||||||
|
return waitingRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitingRequests(Map<String, List<FriendRequest>> waitingRequests) {
|
||||||
|
this.waitingRequests = waitingRequests;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 仅玩家可使用
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "仅玩家能使用该命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
// 无参数时显示帮助
|
||||||
|
if (args.length == 0) {
|
||||||
|
showHelp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子命令
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "add":
|
||||||
|
handleAdd(player, args);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
handleRemove(player, args);
|
||||||
|
break;
|
||||||
|
case "list":
|
||||||
|
handleList(player);
|
||||||
|
break;
|
||||||
|
case "accept":
|
||||||
|
handleAccept(player, args);
|
||||||
|
break;
|
||||||
|
case "deny":
|
||||||
|
handleDeny(player, args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
player.sendMessage(prefix + ChatColor.RED + "未知命令!输入 /friend 查看帮助");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理添加好友命令
|
||||||
|
*/
|
||||||
|
private void handleAdd(Player sender, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend add <玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = args[1];
|
||||||
|
Player target = plugin.getServer().getPlayer(targetName);
|
||||||
|
|
||||||
|
// 目标玩家校验
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target.equals(sender)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "不能添加自己为好友!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已是好友
|
||||||
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
|
if (senderFriends.contains(target)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 已是好友关系!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有未过期的请求
|
||||||
|
List<FriendRequest> targetRequests = waitingRequests.getOrDefault(target, new ArrayList<>());
|
||||||
|
boolean hasPending = targetRequests.stream()
|
||||||
|
.anyMatch(req -> req.getSenderUuid().equals(sender.getUniqueId().toString()) &&
|
||||||
|
System.currentTimeMillis() - req.getTimestamp() < REQUEST_EXPIRE_TIME);
|
||||||
|
|
||||||
|
if (hasPending) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "已向 " + target.getName() + " 发送请求,对方未处理(5分钟内不可重复发送)!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
targetRequests.add(new FriendRequest(sender));
|
||||||
|
waitingRequests.put(target, targetRequests);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
sender.sendMessage(prefix + ChatColor.GREEN + "好友请求已发送给 " + target.getName() + ",请等待回应~");
|
||||||
|
target.sendMessage(prefix + ChatColor.YELLOW + "📩 收到 " + sender.getName() + " 的好友请求!");
|
||||||
|
target.sendMessage(prefix + ChatColor.YELLOW + "使用 /friend accept " + sender.getName() + " 接受,或 /friend deny " + sender.getName() + " 拒绝");
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理移除好友命令
|
||||||
|
*/
|
||||||
|
private void handleRemove(Player sender, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend remove <玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = args[1];
|
||||||
|
Player target = plugin.getServer().getPlayer(targetName);
|
||||||
|
|
||||||
|
// 目标玩家校验
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target.equals(sender)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "不能移除自己!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查好友关系
|
||||||
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
|
List<Player> targetFriends = friends.getOrDefault(target, new ArrayList<>());
|
||||||
|
|
||||||
|
if (!senderFriends.contains(target) || !targetFriends.contains(sender)) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 不是好友关系!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双向移除好友
|
||||||
|
senderFriends.remove(target);
|
||||||
|
targetFriends.remove(sender);
|
||||||
|
friends.put(sender, senderFriends);
|
||||||
|
friends.put(target, targetFriends);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
sender.sendMessage(prefix + ChatColor.GRAY + "已成功移除好友 " + target.getName() + "!");
|
||||||
|
target.sendMessage(prefix + ChatColor.RED + "⚠️ 你被 " + sender.getName() + " 移除好友!");
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查看好友列表命令
|
||||||
|
*/
|
||||||
|
private void handleList(Player player) {
|
||||||
|
List<Player> friendList = friends.getOrDefault(player, new ArrayList<>());
|
||||||
|
|
||||||
|
// 空列表提示
|
||||||
|
if (friendList.isEmpty()) {
|
||||||
|
player.sendMessage(prefix + ChatColor.GRAY + "你的好友列表为空~ 使用 /friend add <玩家名> 添加好友吧!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示好友列表(带在线状态)
|
||||||
|
player.sendMessage(prefix + ChatColor.GREEN + "=== 好友列表(共 " + friendList.size() + " 人)===");
|
||||||
|
for (int i = 0; i < friendList.size(); i++) {
|
||||||
|
Player friend = friendList.get(i);
|
||||||
|
String status = friend.isOnline() ? ChatColor.GREEN + "在线 " : ChatColor.RED + " 离线";
|
||||||
|
player.sendMessage(ChatColor.WHITE + String.format("%d",(i + 1)) + ". " + friend.getName() + " - " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理接受好友请求命令
|
||||||
|
*/
|
||||||
|
private void handleAccept(Player receiver, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend accept <请求ID|玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找目标请求(支持ID和玩家名)
|
||||||
|
FriendRequest targetRequest = findRequest(receiver, args[1]);
|
||||||
|
if (targetRequest == null) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查请求是否过期
|
||||||
|
if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid()));
|
||||||
|
if (sender == null || !sender.isOnline()) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "请求发送者已离线,请求失效!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双向添加好友
|
||||||
|
List<Player> receiverFriends = friends.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
List<Player> senderFriends = friends.getOrDefault(sender, new ArrayList<>());
|
||||||
|
|
||||||
|
receiverFriends.add(sender);
|
||||||
|
senderFriends.add(receiver);
|
||||||
|
friends.put(receiver, receiverFriends);
|
||||||
|
friends.put(sender, senderFriends);
|
||||||
|
|
||||||
|
// 移除请求
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
receiver.sendMessage(prefix + ChatColor.GREEN + "🎉 已接受 " + sender.getName() + " 的好友请求!现在可以一起玩啦~");
|
||||||
|
sender.sendMessage(prefix + ChatColor.GREEN + "🎉 " + receiver.getName() + " 接受了你的好友请求!你们成为好友啦~");
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理拒绝好友请求命令
|
||||||
|
*/
|
||||||
|
private void handleDeny(Player receiver, String[] args) {
|
||||||
|
// 参数校验
|
||||||
|
if (args.length != 2) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend deny <请求ID|玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找目标请求
|
||||||
|
FriendRequest targetRequest = findRequest(receiver, args[1]);
|
||||||
|
if (targetRequest == null) {
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查请求是否过期
|
||||||
|
if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid()));
|
||||||
|
|
||||||
|
// 移除请求
|
||||||
|
removeRequest(receiver, targetRequest);
|
||||||
|
|
||||||
|
// 通知双方
|
||||||
|
receiver.sendMessage(prefix + ChatColor.GRAY + "已拒绝该好友请求!");
|
||||||
|
if (sender != null && sender.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "❌ " + receiver.getName() + " 拒绝了你的好友请求!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找好友请求(支持ID和玩家名)
|
||||||
|
*/
|
||||||
|
private FriendRequest findRequest(Player receiver, String param) {
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
|
||||||
|
// 尝试按ID查找(数字)
|
||||||
|
try {
|
||||||
|
int requestId = Integer.parseInt(param) - 1; // 转为0-based索引
|
||||||
|
if (requestId >= 0 && requestId < requests.size()) {
|
||||||
|
return requests.get(requestId);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 按玩家名查找(忽略大小写)
|
||||||
|
return requests.stream()
|
||||||
|
.filter(req -> {
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid()));
|
||||||
|
return sender != null && sender.getName().equalsIgnoreCase(param);
|
||||||
|
})
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除好友请求
|
||||||
|
*/
|
||||||
|
private void removeRequest(Player receiver, FriendRequest request) {
|
||||||
|
List<FriendRequest> requests = waitingRequests.getOrDefault(receiver, new ArrayList<>());
|
||||||
|
requests.remove(request);
|
||||||
|
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
waitingRequests.remove(receiver);
|
||||||
|
} else {
|
||||||
|
waitingRequests.put(receiver, requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示命令帮助
|
||||||
|
*/
|
||||||
|
private void showHelp(Player player) {
|
||||||
|
player.sendMessage(prefix + ChatColor.GREEN + "=== 好友系统命令帮助 ===");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend add <玩家名> " + ChatColor.GRAY + "- 向玩家发送好友请求");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend remove <玩家名> " + ChatColor.GRAY + "- 移除指定好友");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend list " + ChatColor.GRAY + "- 查看好友列表及在线状态");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend accept <ID|玩家名> " + ChatColor.GRAY + "- 接受好友请求");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/friend deny <ID|玩家名> " + ChatColor.GRAY + "- 拒绝好友请求");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "小贴士:好友请求有效期为5分钟,超时自动失效~");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载好友数据(从JSON文件)
|
||||||
|
*/
|
||||||
|
private void loadData() {
|
||||||
|
if (!dataFile.exists()) {
|
||||||
|
// 初始化空数据
|
||||||
|
friends = new ConcurrentHashMap<>();
|
||||||
|
waitingRequests = new ConcurrentHashMap<>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(dataFile)) {
|
||||||
|
Type dataType = new TypeToken<FriendData>() {}.getType();
|
||||||
|
FriendData data = gson.fromJson(reader, dataType);
|
||||||
|
|
||||||
|
// 转换UUID映射为Player映射(仅加载在线玩家)
|
||||||
|
this.friends = convertUuidToPlayerMap(data.getFriends());
|
||||||
|
this.waitingRequests = convertUuidToRequestMap(data.getWaitingRequests());
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载好友数据失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
// 初始化空数据兜底
|
||||||
|
friends = new ConcurrentHashMap<>();
|
||||||
|
waitingRequests = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存好友数据(到JSON文件)
|
||||||
|
*/
|
||||||
|
private void saveData() {
|
||||||
|
// 转换Player映射为UUID映射(支持序列化)
|
||||||
|
Map<String, List<String>> serializableFriends = convertPlayerToUuidMap(friends);
|
||||||
|
Map<String, List<FriendRequest>> serializableRequests = convertRequestToUuidMap(waitingRequests);
|
||||||
|
|
||||||
|
FriendData data = new FriendData(serializableFriends, serializableRequests);
|
||||||
|
|
||||||
|
try (FileWriter writer = new FileWriter(dataFile)) {
|
||||||
|
gson.toJson(data, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存好友数据失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换:Map<String(UUID), List<String(UUID)>> → Map<Player, List<Player>>
|
||||||
|
*/
|
||||||
|
private Map<Player, List<Player>> convertUuidToPlayerMap(Map<String, List<String>> uuidMap) {
|
||||||
|
Map<Player, List<Player>> playerMap = new ConcurrentHashMap<>();
|
||||||
|
if (uuidMap == null) return playerMap;
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> entry : uuidMap.entrySet()) {
|
||||||
|
// 查找在线的玩家(接收者)
|
||||||
|
Player owner = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
|
||||||
|
if (owner == null) continue;
|
||||||
|
|
||||||
|
// 转换好友列表(仅保留在线玩家)
|
||||||
|
List<Player> friends = entry.getValue().stream()
|
||||||
|
.map(uuidStr -> plugin.getServer().getPlayer(UUID.fromString(uuidStr)))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
playerMap.put(owner, friends);
|
||||||
|
}
|
||||||
|
return playerMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换:Map<Player, List<Player>> → Map<String(UUID), List<String(UUID)>>
|
||||||
|
*/
|
||||||
|
private Map<String, List<String>> convertPlayerToUuidMap(Map<Player, List<Player>> playerMap) {
|
||||||
|
Map<String, List<String>> uuidMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<Player, List<Player>> entry : playerMap.entrySet()) {
|
||||||
|
String ownerUuid = entry.getKey().getUniqueId().toString();
|
||||||
|
List<String> friendUuids = entry.getValue().stream()
|
||||||
|
.map(player -> player.getUniqueId().toString())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
uuidMap.put(ownerUuid, friendUuids);
|
||||||
|
}
|
||||||
|
return uuidMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换:Map<String(UUID), List<FriendRequest>> → Map<Player, List<FriendRequest>>
|
||||||
|
*/
|
||||||
|
private Map<Player, List<FriendRequest>> convertUuidToRequestMap(Map<String, List<FriendRequest>> uuidMap) {
|
||||||
|
Map<Player, List<FriendRequest>> playerMap = new ConcurrentHashMap<>();
|
||||||
|
if (uuidMap == null) return playerMap;
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<FriendRequest>> entry : uuidMap.entrySet()) {
|
||||||
|
Player receiver = plugin.getServer().getPlayer(UUID.fromString(entry.getKey()));
|
||||||
|
if (receiver != null) {
|
||||||
|
playerMap.put(receiver, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return playerMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换:Map<Player, List<FriendRequest>> → Map<String(UUID), List<FriendRequest>>
|
||||||
|
*/
|
||||||
|
private Map<String, List<FriendRequest>> convertRequestToUuidMap(Map<Player, List<FriendRequest>> playerMap) {
|
||||||
|
Map<String, List<FriendRequest>> uuidMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<Player, List<FriendRequest>> entry : playerMap.entrySet()) {
|
||||||
|
String receiverUuid = entry.getKey().getUniqueId().toString();
|
||||||
|
uuidMap.put(receiverUuid, entry.getValue());
|
||||||
|
}
|
||||||
|
return uuidMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动请求过期检查任务(每1分钟执行一次)
|
||||||
|
*/
|
||||||
|
private void startExpireCheckTask() {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 遍历所有待处理请求
|
||||||
|
for (Iterator<Map.Entry<Player, List<FriendRequest>>> iterator = waitingRequests.entrySet().iterator(); iterator.hasNext(); ) {
|
||||||
|
Map.Entry<Player, List<FriendRequest>> entry = iterator.next();
|
||||||
|
Player receiver = entry.getKey();
|
||||||
|
List<FriendRequest> requests = entry.getValue();
|
||||||
|
|
||||||
|
// 移除过期请求
|
||||||
|
Iterator<FriendRequest> reqIterator = requests.iterator();
|
||||||
|
while (reqIterator.hasNext()) {
|
||||||
|
FriendRequest req = reqIterator.next();
|
||||||
|
if (currentTime - req.getTimestamp() > REQUEST_EXPIRE_TIME) {
|
||||||
|
// 通知发送者
|
||||||
|
Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid()));
|
||||||
|
if (sender != null && sender.isOnline()) {
|
||||||
|
sender.sendMessage(prefix + ChatColor.RED + "你发送给 " + receiver.getName() + " 的好友请求已过期(5分钟未处理)!");
|
||||||
|
}
|
||||||
|
reqIterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若请求列表为空,移除该条目
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存更新后的数据
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
}.runTaskTimerAsynchronously(plugin, 0L, 20L * 60); // 异步执行,避免阻塞主线程
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,10 +11,11 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
public class HandleCommandExecutor implements CommandExecutor {
|
public class HandleCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
private Survival plugin;
|
private final Survival plugin;
|
||||||
|
|
||||||
public HandleCommandExecutor(Survival plugin) {
|
public HandleCommandExecutor(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@@ -22,37 +23,254 @@ public class HandleCommandExecutor implements CommandExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
if (sender instanceof Player) {
|
// 1. 校验执行者必须是在线管理员(OP)
|
||||||
if (args.length != 1) {
|
if (!(sender instanceof Player admin)) {
|
||||||
sender.sendMessage(ChatColor.RED + "无效语法 :\n/handle <player> - 处理玩家\n/handle list - 待处理列表");
|
sender.sendMessage(ChatColor.RED + "只有游戏内管理员可执行此处理命令");
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (args[0].equals("list")) {
|
|
||||||
sender.sendMessage(ChatColor.YELLOW + "---------------------------------------\n" + ChatColor.GREEN + plugin.banlist.get(sender) + ChatColor.YELLOW + " 原因: ", ChatColor.RED + plugin.banreason.get(plugin.banlist.get(sender)) + ChatColor.YELLOW + "\n---------------------------------------\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!Bukkit.getPlayer(args[0]).isOnline() && !args[0].equals("list") && Bukkit.getPlayer(args[0]) != plugin.banlist.get((Player) sender)) {
|
|
||||||
sender.sendMessage(ChatColor.RED + "玩家不在线");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
BanList banlis = Bukkit.getBanList(BanList.Type.NAME);
|
|
||||||
long dur = 24 * 60 * 60 * 1000L;
|
|
||||||
Date expdat = new Date(System.currentTimeMillis() + dur);
|
|
||||||
plugin.banreason.put(plugin.banlist.get(sender), null);
|
|
||||||
plugin.banlist.put((Player) sender, null);
|
|
||||||
sender.sendMessage(ChatColor.YELLOW + "处理完成\n");
|
|
||||||
for (Player repo : plugin.reportlist.get(Bukkit.getPlayer(args[0]))) {
|
|
||||||
repo.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
|
||||||
ChatColor.GREEN + "\n 您举报的玩家 " + ChatColor.RED + ChatColor.BOLD +
|
|
||||||
args[0] + ChatColor.GREEN + " 已经被封禁\n" + ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | \n" + ChatColor.GREEN + " 感谢您为维护游戏平衡做贡献!");
|
|
||||||
}
|
|
||||||
plugin.reportlist.put(Bukkit.getPlayer(args[0]), null);
|
|
||||||
banlis.addBan(args[0], ChatColor.AQUA + "星阁钱语\n" + ChatColor.RED + "您的账号 " + args[0] + " 已被封禁\n原因: 管理员处理作弊行为", expdat, null);
|
|
||||||
Bukkit.getPlayer(args[0]).kickPlayer(ChatColor.AQUA + "星阁钱语\n" + ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " 1 " + ChatColor.RED + "天\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + args[0] + ChatColor.RED + "\n封禁原因:" + "管理员处理");
|
|
||||||
} else {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!admin.isOp()) {
|
||||||
|
admin.sendMessage(ChatColor.RED + "权限不足!仅管理员可处理举报");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验管理员是否有待处理的举报
|
||||||
|
Player target = plugin.banlist.get(admin);
|
||||||
|
String reportReason = plugin.banreason.get(target);
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
admin.sendMessage(ChatColor.RED + "无待处理的举报玩家,或该玩家已离线");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验参数合法性
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendUsage(admin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 分支处理不同命令参数(新增带单位的固定封禁)
|
||||||
|
switch (args[0]) {
|
||||||
|
case "no":
|
||||||
|
handleReject(admin, target);
|
||||||
|
break;
|
||||||
|
case "point":
|
||||||
|
handleDeductPoint(admin, target, reportReason);
|
||||||
|
break;
|
||||||
|
case "12h":
|
||||||
|
case "1d":
|
||||||
|
case "3d":
|
||||||
|
case "7d":
|
||||||
|
case "14d":
|
||||||
|
case "1mo":
|
||||||
|
case "3mo":
|
||||||
|
case "1y":
|
||||||
|
handleFixedBan(admin, target, reportReason, args[0]);
|
||||||
|
break;
|
||||||
|
case "ban":
|
||||||
|
handleCustomBan(admin, target, reportReason, args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
admin.sendMessage(ChatColor.RED + "无效参数!");
|
||||||
|
sendUsage(admin);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修正:同步新的命令用法提示(新增带单位的固定封禁参数)
|
||||||
|
*/
|
||||||
|
private void sendUsage(Player admin) {
|
||||||
|
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报处理命令用法:");
|
||||||
|
admin.sendMessage(ChatColor.WHITE + "/handle no - 驳回举报");
|
||||||
|
admin.sendMessage(ChatColor.WHITE + "/handle point - 扣除玩家信用分");
|
||||||
|
admin.sendMessage(ChatColor.WHITE + "/handle 12h/1d/3d/7d/14d - 封禁12小时/1天/3天/7天/14天");
|
||||||
|
admin.sendMessage(ChatColor.WHITE + "/handle 1mo/3mo/1y - 封禁1个月/3个月/1年");
|
||||||
|
admin.sendMessage(ChatColor.WHITE + "/handle ban <天数> - 自定义封禁天数(按天计算)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有方法:驳回举报(无修改)
|
||||||
|
private void handleReject(Player admin, Player target) {
|
||||||
|
plugin.banlist.remove(admin);
|
||||||
|
plugin.banreason.remove(target);
|
||||||
|
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
|
ChatColor.GREEN + "已驳回玩家 " + ChatColor.RED + target.getName() + " 的举报");
|
||||||
|
notifyReporters(target, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
|
||||||
|
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+target.getName()+ChatColor.GREEN+" 经查实无违规行为,已驳回请求\n"+
|
||||||
|
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏环境做出贡献,但请不要滥用举报。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有方法:扣除信用分(无修改)
|
||||||
|
private void handleDeductPoint(Player admin, Player target, String reason) {
|
||||||
|
plugin.banlist.remove(admin);
|
||||||
|
plugin.banreason.remove(target);
|
||||||
|
target.getScoreboard().getObjective("handled").getScore(target).setScore(target.getScoreboard().getObjective(("handled")).getScore(target).getScore()+5);
|
||||||
|
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
|
ChatColor.GREEN + "已扣除玩家 " + ChatColor.RED + target.getName() +
|
||||||
|
ChatColor.GREEN + " 的信用分,举报原因:" + ChatColor.YELLOW + reason);
|
||||||
|
notifyReporters(target, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
|
||||||
|
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+target.getName()+ChatColor.GREEN+" 已经被处理\n"+
|
||||||
|
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修正:处理带单位的固定封禁(12h/1d/1mo等)
|
||||||
|
*/
|
||||||
|
private void handleFixedBan(Player admin, Player target, String reason, String timeStr) {
|
||||||
|
// 调用重载的banPlayer方法(支持带单位参数)
|
||||||
|
boolean banSuccess = banPlayer(target, reason, timeStr);
|
||||||
|
if (banSuccess) {
|
||||||
|
// 清理待处理数据
|
||||||
|
plugin.banlist.remove(admin);
|
||||||
|
plugin.banreason.remove(target);
|
||||||
|
|
||||||
|
// 修正:通知消息显示真实单位(如12小时、1个月)
|
||||||
|
String timeDisplay = getTimeDisplay(timeStr);
|
||||||
|
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
|
ChatColor.GREEN + "已成功封禁玩家 " + ChatColor.RED + target.getName() +
|
||||||
|
ChatColor.GREEN + " " + timeDisplay + ",举报原因:" + ChatColor.YELLOW + reason);
|
||||||
|
} else {
|
||||||
|
admin.sendMessage(ChatColor.RED + "封禁失败!无效的封禁时长格式");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有方法:自定义天数封禁(无修改,仍按天计算)
|
||||||
|
private void handleCustomBan(Player admin, Player target, String reason, String[] args) {
|
||||||
|
if (args.length != 2) {
|
||||||
|
admin.sendMessage(ChatColor.RED + "自定义封禁用法错误!正确格式:/handle ban <天数>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int days;
|
||||||
|
try {
|
||||||
|
days = Integer.parseInt(args[1]);
|
||||||
|
if (days <= 0) {
|
||||||
|
admin.sendMessage(ChatColor.RED + "封禁天数必须为正整数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
admin.sendMessage(ChatColor.RED + "请输入有效的数字作为封禁天数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用原有的banPlayer方法(按天数计算)
|
||||||
|
banPlayer(target, reason, days);
|
||||||
|
plugin.banlist.remove(admin);
|
||||||
|
plugin.banreason.remove(target);
|
||||||
|
|
||||||
|
admin.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
|
ChatColor.GREEN + "已自定义封禁玩家 " + ChatColor.RED + target.getName() +
|
||||||
|
ChatColor.GREEN + " " + days + " 天,举报原因:" + ChatColor.YELLOW + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:重载banPlayer方法(支持带单位的时间字符串)
|
||||||
|
* @param timeStr 带单位的封禁时长(12h/1d/3d/7d/14d/1mo/3mo/1y)
|
||||||
|
* @return 封禁是否成功
|
||||||
|
*/
|
||||||
|
private boolean banPlayer(Player player, String reason, String timeStr) {
|
||||||
|
// 1. 解析时间字符串为毫秒数
|
||||||
|
long banMillis = parseTimeToMillis(timeStr);
|
||||||
|
if (banMillis <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算封禁到期时间
|
||||||
|
Date expireDate = new Date(System.currentTimeMillis() + banMillis);
|
||||||
|
|
||||||
|
// 3. 构建封禁消息(显示真实单位)
|
||||||
|
String timeDisplay = getTimeDisplay(timeStr);
|
||||||
|
String banReason = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
|
||||||
|
ChatColor.RED + "您的账号 " + player.getName() + " 已被封禁\n" +
|
||||||
|
ChatColor.RED + "原因: " + reason + "\n" +
|
||||||
|
ChatColor.RED + "封禁时长: " + timeDisplay;
|
||||||
|
|
||||||
|
String kickMessage = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
|
||||||
|
ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " " + timeDisplay + " " +
|
||||||
|
ChatColor.RED + "\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + player.getName() +
|
||||||
|
ChatColor.RED + "\n封禁原因: " + reason;
|
||||||
|
|
||||||
|
// 4. 执行封禁操作
|
||||||
|
Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), banReason, expireDate, null);
|
||||||
|
player.kickPlayer(kickMessage);
|
||||||
|
|
||||||
|
// 5. 通知举报者
|
||||||
|
notifyReporters(player, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
|
||||||
|
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+player.getName()+ChatColor.GREEN+" 已经被处理\n"+
|
||||||
|
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
|
||||||
|
|
||||||
|
// 6. 清理举报缓存
|
||||||
|
plugin.reportlist.remove(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原有:按天数封禁(保留,用于自定义封禁)
|
||||||
|
*/
|
||||||
|
private void banPlayer(Player player, String reason, int days) {
|
||||||
|
long banDuration = (long) days * 24 * 60 * 60 * 1000L;
|
||||||
|
Date expireDate = new Date(System.currentTimeMillis() + banDuration);
|
||||||
|
|
||||||
|
String banReason = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
|
||||||
|
ChatColor.RED + "您的账号 " + player.getName() + " 已被封禁\n" +
|
||||||
|
ChatColor.RED + "原因: " + reason + "\n" +
|
||||||
|
ChatColor.RED + "封禁时长: " + days + " 天";
|
||||||
|
|
||||||
|
String kickMessage = ChatColor.AQUA + "CloudNest" + ChatColor.DARK_AQUA + "NetWork\n" +
|
||||||
|
ChatColor.RED + "你被封禁了" + ChatColor.BOLD + ChatColor.YELLOW + " " + days + " " +
|
||||||
|
ChatColor.RED + "天\n被封禁的账号: " + ChatColor.RED + ChatColor.BOLD + player.getName() +
|
||||||
|
ChatColor.RED + "\n封禁原因: " + reason;
|
||||||
|
|
||||||
|
Bukkit.getBanList(BanList.Type.NAME).addBan(player.getName(), banReason, expireDate, null);
|
||||||
|
player.kickPlayer(kickMessage);
|
||||||
|
notifyReporters(player, ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+
|
||||||
|
ChatColor.GREEN+"您举报的玩家 "+ChatColor.YELLOW+ChatColor.BOLD+player.getName()+ChatColor.GREEN+" 已经被处理\n"+
|
||||||
|
ChatColor.LIGHT_PURPLE+"举报系统 "+ChatColor.WHITE+"| "+ChatColor.YELLOW+"感谢您为维护游戏平衡做出贡献。");
|
||||||
|
plugin.reportlist.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:将带单位的时间字符串转换为毫秒数
|
||||||
|
* 对应关系:12h=12小时,1d=1天,1mo=30天,1y=365天
|
||||||
|
*/
|
||||||
|
private long parseTimeToMillis(String timeStr) {
|
||||||
|
return switch (timeStr.toLowerCase()) {
|
||||||
|
case "12h" -> 12L * 60 * 60 * 1000; // 12小时 = 12*3600*1000ms
|
||||||
|
case "1d" -> 1L * 24 * 60 * 60 * 1000; // 1天
|
||||||
|
case "3d" -> 3L * 24 * 60 * 60 * 1000; // 3天
|
||||||
|
case "7d" -> 7L * 24 * 60 * 60 * 1000; // 7天
|
||||||
|
case "14d" -> 14L * 24 * 60 * 60 * 1000; // 14天
|
||||||
|
case "1mo" -> 30L * 24 * 60 * 60 * 1000; // 1个月(按30天算)
|
||||||
|
case "3mo" -> 90L * 24 * 60 * 60 * 1000; // 3个月(90天)
|
||||||
|
case "1y" -> 365L * 24 * 60 * 60 * 1000; // 1年(365天)
|
||||||
|
default -> -1; // 无效格式
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:将时间字符串转换为友好显示文本(如12h→12小时,1mo→1个月)
|
||||||
|
*/
|
||||||
|
private String getTimeDisplay(String timeStr) {
|
||||||
|
return switch (timeStr.toLowerCase()) {
|
||||||
|
case "12h" -> "12小时";
|
||||||
|
case "1d" -> "1天";
|
||||||
|
case "3d" -> "3天";
|
||||||
|
case "7d" -> "7天";
|
||||||
|
case "14d" -> "14天";
|
||||||
|
case "1mo" -> "1个月";
|
||||||
|
case "3mo" -> "3个月";
|
||||||
|
case "1y" -> "1年";
|
||||||
|
default -> timeStr; // 异常情况直接显示原字符串
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有方法:通知举报者(无修改)
|
||||||
|
private void notifyReporters(Player target, String message) {
|
||||||
|
var reporters = plugin.reportlist.getOrDefault(target, new HashSet<>());
|
||||||
|
for (Player reporter : reporters) {
|
||||||
|
if (reporter != null && reporter.isOnline()) {
|
||||||
|
reporter.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,16 @@ public class HelpCommandExecutor implements CommandExecutor {
|
|||||||
sender.sendMessage(ChatColor.GREEN + "/pvp - 开启/关闭 玩家伤害");
|
sender.sendMessage(ChatColor.GREEN + "/pvp - 开启/关闭 玩家伤害");
|
||||||
sender.sendMessage(ChatColor.GREEN + "/tag - 选择称号");
|
sender.sendMessage(ChatColor.GREEN + "/tag - 选择称号");
|
||||||
sender.sendMessage(ChatColor.GREEN + "/teleport <玩家名> - 玩家传送");
|
sender.sendMessage(ChatColor.GREEN + "/teleport <玩家名> - 玩家传送");
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "/teleport <玩家名> me - 玩家传送");
|
||||||
sender.sendMessage(ChatColor.GREEN + "/report - 举报作弊玩家");
|
sender.sendMessage(ChatColor.GREEN + "/report - 举报作弊玩家");
|
||||||
sender.sendMessage(ChatColor.GREEN + "/tpacc <accept|deny> - 同意/拒绝玩家传送");
|
sender.sendMessage(ChatColor.GREEN+"/login <密码> -登录");
|
||||||
sender.sendMessage(ChatColor.GREEN + "/tpfin - 结束传送");
|
sender.sendMessage(ChatColor.GREEN+"/reg <密码> <密码> - 注册");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/party <参数> <参数> - 队伍");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/friend <参数> <参数> - 好友");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/msg <内容> - 私聊");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/home - 回家");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/rank - 榜单");
|
||||||
|
sender.sendMessage(ChatColor.GREEN+"/mb - 留言板");
|
||||||
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
|
sender.sendMessage(ChatColor.YELLOW + "-----------------------------");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class InventoryCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public InventoryCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 检查命令发送者是否为玩家
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Player senderPlayer = (Player) sender;
|
||||||
|
|
||||||
|
// 检查权限(OP)
|
||||||
|
if (!senderPlayer.isOp()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "你没有权限使用此命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查参数是否正确
|
||||||
|
if (args.length < 1) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法: /inventory <玩家名>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取目标玩家
|
||||||
|
Player targetPlayer = Bukkit.getPlayer(args[0]);
|
||||||
|
if (targetPlayer == null || !targetPlayer.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "目标玩家不存在或未在线!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建显示用的GUI(54格=6行×9列)
|
||||||
|
Inventory gui = Bukkit.createInventory(
|
||||||
|
null,
|
||||||
|
54,
|
||||||
|
targetPlayer.getDisplayName() + ChatColor.WHITE + " 的背包"
|
||||||
|
);
|
||||||
|
|
||||||
|
PlayerInventory targetInv = targetPlayer.getInventory();
|
||||||
|
|
||||||
|
// 1. 填充主背包(上3行:0-26格)
|
||||||
|
for (int i = 0; i < 27; i++) {
|
||||||
|
ItemStack item = targetInv.getItem(i);
|
||||||
|
gui.setItem(i, getItemOrBarrier(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 第4行:分割线(淡灰色玻璃板,27-35格)
|
||||||
|
ItemStack divider = new ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE);
|
||||||
|
for (int i = 27; i < 36; i++) {
|
||||||
|
gui.setItem(i, divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 第5行:快捷栏(36-44格,对应玩家快捷栏27-35格)
|
||||||
|
for (int i = 27; i < 36; i++) {
|
||||||
|
ItemStack item = targetInv.getItem(i);
|
||||||
|
// 快捷栏在GUI中偏移9格(因为第4行占了9格)
|
||||||
|
gui.setItem(i + 9, getItemOrBarrier(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 第6行:装备和副手(45-53格)
|
||||||
|
// 装备槽对应关系:靴子(36)、护腿(37)、胸甲(38)、头盔(39)、副手(40)
|
||||||
|
ItemStack boots = targetInv.getItem(36);
|
||||||
|
ItemStack leggings = targetInv.getItem(37);
|
||||||
|
ItemStack chestplate = targetInv.getItem(38);
|
||||||
|
ItemStack helmet = targetInv.getItem(39);
|
||||||
|
ItemStack offhand = targetInv.getItem(40);
|
||||||
|
|
||||||
|
// 放置装备(按常规装备顺序排列)
|
||||||
|
gui.setItem(45, getItemOrBarrier(boots)); // 靴子
|
||||||
|
gui.setItem(46, getItemOrBarrier(leggings)); // 护腿
|
||||||
|
gui.setItem(47, getItemOrBarrier(chestplate)); // 胸甲
|
||||||
|
gui.setItem(48, getItemOrBarrier(helmet)); // 头盔
|
||||||
|
gui.setItem(52, getItemOrBarrier(offhand)); // 副手
|
||||||
|
|
||||||
|
// 第6行剩余位置用屏障填充
|
||||||
|
for (int i = 49; i < 52; i++) {
|
||||||
|
gui.setItem(i, new ItemStack(Material.BARRIER));
|
||||||
|
}
|
||||||
|
gui.setItem(53, new ItemStack(Material.BARRIER));
|
||||||
|
|
||||||
|
// 打开GUI给命令发送者
|
||||||
|
senderPlayer.openInventory(gui);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:如果物品为空则返回屏障,否则返回原物品
|
||||||
|
*/
|
||||||
|
private ItemStack getItemOrBarrier(ItemStack item) {
|
||||||
|
return (item == null || item.getType() == Material.AIR)
|
||||||
|
? new ItemStack(Material.AIR)
|
||||||
|
: item;
|
||||||
|
}
|
||||||
|
}
|
||||||
272
src/main/java/org/xgqy/survival/command/KillCommandExecutor.java
Normal file
272
src/main/java/org/xgqy/survival/command/KillCommandExecutor.java
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.scoreboard.Objective;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class KillCommandExecutor implements CommandExecutor, Listener {
|
||||||
|
// 核心配置常量
|
||||||
|
private static final int EXP_COST = 50; // 经验扣除量
|
||||||
|
private static final long TELEPORT_DELAY = 5 * 20L; // 传送延迟(5秒 = 100tick)
|
||||||
|
private static final long COOLDOWN = 30 * 1000L; // 命令冷却(30秒)
|
||||||
|
private static final double MOVE_THRESHOLD = 0.1; // 移动判定阈值(超过0.1格视为移动)
|
||||||
|
private static final Particle TELEPORT_PARTICLE = Particle.CLOUD;
|
||||||
|
private static final int PARTICLE_COUNT = 25; // 每圈粒子数量
|
||||||
|
private static final double PARTICLE_RADIUS = 1.6; // 粒子生成半径
|
||||||
|
private static final double PARTICLE_Y_OFFSET = 0.6; // 粒子Y轴偏移(避免贴地)
|
||||||
|
private static final Color PARTICLE_COLOR = Color.BLUE; // 粒子颜色(蓝色)
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 正在传送的玩家数据(线程安全)
|
||||||
|
private final Map<Player, TeleportData> teleportingPlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
// 玩家冷却时间记录(线程安全)
|
||||||
|
private final Map<Player, Long> cooldownMap = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public KillCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// 注册移动事件监听器(检测传送期间是否移动)
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
private static class TeleportData {
|
||||||
|
private final Location startLocation; // 起始位置(用于检测移动)
|
||||||
|
private final int deductedExp; // 已扣除的经验(取消时返还)
|
||||||
|
private boolean hasMoved; // 是否已移动
|
||||||
|
private BukkitTask particleTask; // 粒子效果任务(用于取消)
|
||||||
|
|
||||||
|
public TeleportData(Location startLocation, int deductedExp) {
|
||||||
|
this.startLocation = startLocation;
|
||||||
|
this.deductedExp = deductedExp;
|
||||||
|
this.hasMoved = false;
|
||||||
|
this.particleTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter & Setter
|
||||||
|
public Location getStartLocation() {
|
||||||
|
return startLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeductedExp() {
|
||||||
|
return deductedExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoved() {
|
||||||
|
return hasMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasMoved(boolean hasMoved) {
|
||||||
|
this.hasMoved = hasMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BukkitTask getParticleTask() {
|
||||||
|
return particleTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParticleTask(BukkitTask particleTask) {
|
||||||
|
this.particleTask = particleTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 仅玩家可使用
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "X 无法对非玩家使用该命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 检查冷却状态
|
||||||
|
if (isOnCooldown(player)) {
|
||||||
|
long remainingSeconds = (cooldownMap.get(player) - System.currentTimeMillis()) / 1000;
|
||||||
|
player.sendMessage(ChatColor.RED + "⚠ 命令冷却中!剩余 " + remainingSeconds + " 秒后可再次使用");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查经验是否足够
|
||||||
|
int totalExp = player.getTotalExperience();
|
||||||
|
if (totalExp < EXP_COST) {
|
||||||
|
player.sendMessage(ChatColor.RED + "X 经验不足!使用该命令需消耗 " + EXP_COST + " 点经验,你当前仅拥有 " + totalExp + " 点");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 扣除经验并初始化传送数据
|
||||||
|
player.setTotalExperience(totalExp - EXP_COST);
|
||||||
|
Location startLoc = player.getLocation().clone(); // 记录起始位置(精确到坐标和角度)
|
||||||
|
TeleportData teleportData = new TeleportData(startLoc, EXP_COST);
|
||||||
|
teleportingPlayers.put(player, teleportData);
|
||||||
|
// 4. 发送传送提示
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "消耗:" + ChatColor.GREEN + EXP_COST + " 点经验");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "倒计时:" + ChatColor.RED + "5 秒");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "=====================================");
|
||||||
|
|
||||||
|
// 5. 启动粒子效果任务(每0.1秒生成一圈粒子)
|
||||||
|
startParticleEffect(player, teleportData);
|
||||||
|
|
||||||
|
// 6. 启动传送倒计时任务
|
||||||
|
startTeleportTimer(player, teleportData);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测玩家是否在冷却中
|
||||||
|
*/
|
||||||
|
private boolean isOnCooldown(Player player) {
|
||||||
|
if (!cooldownMap.containsKey(player)) return false;
|
||||||
|
// 冷却时间结束后自动移除记录
|
||||||
|
if (System.currentTimeMillis() >= cooldownMap.get(player)) {
|
||||||
|
cooldownMap.remove(player);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动传送粒子效果(围绕玩家生成圆形蓝色粒子)
|
||||||
|
*/
|
||||||
|
private void startParticleEffect(Player player, TeleportData data) {
|
||||||
|
int k[] = {50};
|
||||||
|
BukkitTask particleTask = new BukkitRunnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 玩家已移动或传送结束,停止粒子
|
||||||
|
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
||||||
|
player.sendTitle(ChatColor.RED+"传送失败","剩余能量已退还",20,40,20);
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.sendTitle(ChatColor.GREEN+"传送将在 "+String.format("%.1f",1.0*k[0]/10)+" 秒后完成",ChatColor.YELLOW+"已消耗能量: "+(String.format("%d",50-k[0])),0,20,0);
|
||||||
|
k[0] -= 1;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0L, 2L); // 每2tick(0.1秒)执行一次
|
||||||
|
|
||||||
|
data.setParticleTask(particleTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动传送倒计时任务
|
||||||
|
*/
|
||||||
|
private void startTeleportTimer(Player player, TeleportData data) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 玩家已移动或传送状态已移除,直接结束
|
||||||
|
if (!teleportingPlayers.containsKey(player) || data.hasMoved()) {
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 停止粒子效果
|
||||||
|
if (data.getParticleTask() != null) {
|
||||||
|
data.getParticleTask().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取活点位置(优先床的重生点,无则使用世界出生点)
|
||||||
|
Location spawnLoc = player.getBedSpawnLocation();
|
||||||
|
if (spawnLoc == null) {
|
||||||
|
spawnLoc = player.getWorld().getSpawnLocation().clone().add(0.5, 0, 0.5); // 居中出生点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 执行传送
|
||||||
|
player.teleport(spawnLoc);
|
||||||
|
|
||||||
|
// 4. 保留原功能:设置计分板goback为100
|
||||||
|
Objective gobackObj = player.getScoreboard().getObjective("goback");
|
||||||
|
if (gobackObj != null) {
|
||||||
|
gobackObj.getScore(player.getName()).setScore(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 发送传送成功提示
|
||||||
|
player.sendTitle(ChatColor.GREEN+"传送成功",ChatColor.GREEN+"你现在处于恢复期,30秒后才能再次使用",10,50,10);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "传送成功!");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=====================================");
|
||||||
|
|
||||||
|
// 6. 设置30秒冷却
|
||||||
|
cooldownMap.put(player, System.currentTimeMillis() + COOLDOWN);
|
||||||
|
|
||||||
|
// 7. 清理传送状态
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, TELEPORT_DELAY); // 5秒后执行传送
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听玩家移动事件(传送期间移动则取消传送)
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
TeleportData data = teleportingPlayers.get(player);
|
||||||
|
|
||||||
|
// 非传送中玩家,直接忽略
|
||||||
|
if (data == null || data.hasMoved()) return;
|
||||||
|
|
||||||
|
Location currentLoc = event.getTo();
|
||||||
|
Location startLoc = data.getStartLocation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查是否移动(距离阈值>0.1格,忽略微小波动)
|
||||||
|
if (currentLoc.distanceSquared(startLoc) > MOVE_THRESHOLD * MOVE_THRESHOLD) {
|
||||||
|
// 标记为已移动,取消传送
|
||||||
|
data.setHasMoved(true);
|
||||||
|
cancelTeleport(player, data);
|
||||||
|
}
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// 避免并发修改异常(忽略)
|
||||||
|
plugin.getLogger().warning("传送期间玩家移动检测并发异常:" + player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消传送并返还经验
|
||||||
|
*/
|
||||||
|
private void cancelTeleport(Player player, TeleportData data) {
|
||||||
|
// 1. 停止粒子效果
|
||||||
|
if (data.getParticleTask() != null) {
|
||||||
|
data.getParticleTask().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 返还扣除的经验
|
||||||
|
//player.setTotalExperience(player.getTotalExperience() + data.getDeductedExp());
|
||||||
|
|
||||||
|
// 3. 发送取消提示
|
||||||
|
player.sendMessage(ChatColor.RED + "=====================================");
|
||||||
|
player.sendMessage(ChatColor.RED + " 传送已取消!");
|
||||||
|
player.sendMessage(ChatColor.RED + "=====================================");
|
||||||
|
|
||||||
|
// 4. 清理传送状态
|
||||||
|
teleportingPlayers.remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
[投票]
|
||||||
|
新功能投票
|
||||||
|
1.【偏实用性】榜单 0
|
||||||
|
2.【偏技术性】登录检测 0
|
||||||
|
3.【偏实用性】留言板 1
|
||||||
|
4.【偏实用性】玩家间购买 0
|
||||||
|
5.【偏技术性】完善并添加现有功能 0
|
||||||
|
6.【偏趣味性】打屁(限时功能)0
|
||||||
|
7.自己提建议___________
|
||||||
|
投票输入数字即可。(如果选择7,请附加一句建议)
|
||||||
|
结束时间: 2025-11-09 17:00
|
||||||
|
|
||||||
|
*/
|
||||||
237
src/main/java/org/xgqy/survival/command/LandCommandExecutor.java
Normal file
237
src/main/java/org/xgqy/survival/command/LandCommandExecutor.java
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class LandCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 临时存储玩家正在创建的领地
|
||||||
|
private final Map<Player, Survival.Land> tempLands = new HashMap<>();
|
||||||
|
|
||||||
|
public LandCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断位置是否在x-z区域内
|
||||||
|
public static boolean isLocationBetween(Location target, Location a, Location b) {
|
||||||
|
if (target == null || a == null || b == null) return false;
|
||||||
|
if (!Objects.equals(target.getWorld(), a.getWorld()) || !Objects.equals(target.getWorld(), b.getWorld())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double minX = Math.min(a.getX(), b.getX());
|
||||||
|
double maxX = Math.max(a.getX(), b.getX());
|
||||||
|
double minZ = Math.min(a.getZ(), b.getZ());
|
||||||
|
double maxZ = Math.max(a.getZ(), b.getZ());
|
||||||
|
return target.getX() >= minX && target.getX() <= maxX && target.getZ() >= minZ && target.getZ() <= maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两个x-z区域是否重叠
|
||||||
|
public static boolean isRegionsTouching(Location loc1, Location loc2, Location loc3, Location loc4) {
|
||||||
|
if (loc1 == null || loc2 == null || loc3 == null || loc4 == null) return false;
|
||||||
|
if (!Objects.equals(loc1.getWorld(), loc3.getWorld())) return false;
|
||||||
|
|
||||||
|
double minX1 = Math.min(loc1.getX(), loc2.getX());
|
||||||
|
double maxX1 = Math.max(loc1.getX(), loc2.getX());
|
||||||
|
double minZ1 = Math.min(loc1.getZ(), loc2.getZ());
|
||||||
|
double maxZ1 = Math.max(loc1.getZ(), loc2.getZ());
|
||||||
|
|
||||||
|
double minX2 = Math.min(loc3.getX(), loc4.getX());
|
||||||
|
double maxX2 = Math.max(loc3.getX(), loc4.getX());
|
||||||
|
double minZ2 = Math.min(loc3.getZ(), loc4.getZ());
|
||||||
|
double maxZ2 = Math.max(loc3.getZ(), loc4.getZ());
|
||||||
|
|
||||||
|
return maxX1 >= minX2 && maxX2 >= minX1 && maxZ1 >= minZ2 && maxZ2 >= minZ1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "仅玩家可使用领地命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendHelp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "set1":
|
||||||
|
handleSet1(player);
|
||||||
|
break;
|
||||||
|
case "set2":
|
||||||
|
handleSet2(player);
|
||||||
|
break;
|
||||||
|
case "allow":
|
||||||
|
handleAllowDisallow(player, args, true);
|
||||||
|
break;
|
||||||
|
case "disallow":
|
||||||
|
handleAllowDisallow(player, args, false);
|
||||||
|
break;
|
||||||
|
case "reset":
|
||||||
|
handleReset(player);
|
||||||
|
break;
|
||||||
|
case "protect":
|
||||||
|
handleProtect(player);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sender.sendMessage(ChatColor.RED + "未知命令!输入 /land 查看帮助。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置领地起点
|
||||||
|
private void handleSet1(Player player) {
|
||||||
|
if (tempLands.containsKey(player)) {
|
||||||
|
Survival.Land temp = tempLands.get(player);
|
||||||
|
if (temp.getStart() != null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "已设置过起点!使用 /land reset 重置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tempLands.put(player, new Survival.Land(null, null, new ArrayList<>(), player.getName(), new HashMap<>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Survival.Land temp = tempLands.get(player);
|
||||||
|
temp.setStart(player.getLocation());
|
||||||
|
player.sendMessage(ChatColor.GREEN + "已设置领地起点!使用 /land set2 设置终点。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置领地终点(完成创建)
|
||||||
|
private void handleSet2(Player player) {
|
||||||
|
if (!tempLands.containsKey(player)) {
|
||||||
|
player.sendMessage(ChatColor.RED + "请先使用 /land set1 设置起点!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Survival.Land temp = tempLands.get(player);
|
||||||
|
if (temp.getStart() == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "请先设置起点!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (temp.getEnd() != null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "已设置过终点!使用 /land reset 重置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location end = player.getLocation();
|
||||||
|
temp.setEnd(end);
|
||||||
|
Location start = temp.getStart();
|
||||||
|
|
||||||
|
// 检查是否包含出生点
|
||||||
|
Location spawn = player.getRespawnLocation() != null ? player.getRespawnLocation() : player.getWorld().getSpawnLocation();
|
||||||
|
if (!isLocationBetween(spawn, start, end)) {
|
||||||
|
tempLands.remove(player);
|
||||||
|
player.sendMessage(ChatColor.RED + "领地必须包含你的出生点!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查与其他领地重叠
|
||||||
|
for (Survival.Land land : plugin.land) {
|
||||||
|
if (land.getOwner() == null) continue;
|
||||||
|
if (land.getOwner().equals(player.getName())) continue; // 跳过自己的领地
|
||||||
|
if (isRegionsTouching(start, end, land.getStart(), land.getEnd())) {
|
||||||
|
tempLands.remove(player);
|
||||||
|
player.sendMessage(ChatColor.RED + "与其他玩家领地重叠!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化默认权限(7项:破坏、放置、飞行、交互、使用方块、TNT爆炸、玩家伤害)
|
||||||
|
List<Boolean> defaultPerm = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
defaultPerm.add(false); // 默认禁止所有权限
|
||||||
|
}
|
||||||
|
temp.setDefaultperm(defaultPerm);
|
||||||
|
|
||||||
|
// 添加到正式列表
|
||||||
|
plugin.land.add(temp);
|
||||||
|
tempLands.remove(player);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "领地创建成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许/禁止玩家权限
|
||||||
|
private void handleAllowDisallow(Player player, String[] args, boolean allow) {
|
||||||
|
if (args.length < 3) {
|
||||||
|
player.sendMessage(ChatColor.RED + "用法:/land " + (allow ? "allow" : "disallow") + " <玩家名> <1-7>");
|
||||||
|
player.sendMessage(ChatColor.GRAY + "权限索引:1=破坏 2=放置 3=飞行 4=交互 5=使用方块 6=TNT爆炸 7=玩家伤害");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = args[1];
|
||||||
|
int permIndex;
|
||||||
|
try {
|
||||||
|
permIndex = Integer.parseInt(args[2]) - 1; // 转为0基索引
|
||||||
|
if (permIndex < 0 || permIndex > 6) throw new NumberFormatException();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
player.sendMessage(ChatColor.RED + "权限索引必须是1-7!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取玩家自己的领地
|
||||||
|
Survival.Land land = getPlayerLand(player);
|
||||||
|
if (land == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你没有领地!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置权限
|
||||||
|
Map<String, List<Boolean>> permMap = land.perm;
|
||||||
|
List<Boolean> targetPerm = permMap.getOrDefault(targetName, new ArrayList<>(land.getDefaultperm()));
|
||||||
|
while (targetPerm.size() < 7) targetPerm.add(false); // 确保长度
|
||||||
|
targetPerm.set(permIndex, allow);
|
||||||
|
permMap.put(targetName, targetPerm);
|
||||||
|
|
||||||
|
player.sendMessage(ChatColor.GREEN + "已" + (allow ? "允许" : "禁止") + "玩家 " + targetName + " 的 " + getPermName(permIndex) + " 权限!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置领地设置
|
||||||
|
private void handleReset(Player player) {
|
||||||
|
tempLands.remove(player);
|
||||||
|
plugin.land.remove(player);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "领地设置已重置!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开关领地保护(示例:控制TNT爆炸)
|
||||||
|
private void handleProtect(Player player) {
|
||||||
|
}
|
||||||
|
private Survival.Land getPlayerLand(Player player) {
|
||||||
|
for (Survival.Land land : plugin.land) {
|
||||||
|
if (land.getOwner() != null && land.getOwner().equals(player.getName())) {
|
||||||
|
return land;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助:获取权限名称
|
||||||
|
private String getPermName(int index) {
|
||||||
|
String[] names = {"破坏", "放置", "飞行", "交互", "使用方块", "TNT爆炸", "玩家伤害"};
|
||||||
|
return names[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送帮助信息
|
||||||
|
private void sendHelp(Player player) {
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "===== 领地命令 =====");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land set1" + ChatColor.WHITE + " - 设置领地起点");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land set2" + ChatColor.WHITE + " - 设置领地终点(完成创建)");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land allow <玩家> <1-7>" + ChatColor.WHITE + " - 允许玩家权限");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land disallow <玩家> <1-7>" + ChatColor.WHITE + " - 禁止玩家权限");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land reset" + ChatColor.WHITE + " - 重置领地设置");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "/land protect" + ChatColor.WHITE + " - 开关超级保护(测试中)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class LoginCommandExecutor implements CommandExecutor {
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public LoginCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "只有玩家可以使用此命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
// 检查玩家是否已登录
|
||||||
|
if (plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你已经登录了!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查参数是否正确
|
||||||
|
if (args.length != 1) {
|
||||||
|
player.sendMessage(ChatColor.RED + "用法: /login <密码>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查玩家是否已注册
|
||||||
|
if (!plugin.getAccountManager().isRegistered(player.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你还没有注册! 请使用 /reg <密码> <确认密码> 注册");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试登录
|
||||||
|
if (plugin.getAccountManager().login(player, args[0])) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "登录成功! 欢迎回来," + player.getName() + "!");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatColor.RED + "登录失败! 密码不正确");
|
||||||
|
player.kickPlayer(ChatColor.RED+"密码错误!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MessageBoardCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
// 核心配置常量
|
||||||
|
private static final long COOLDOWN_DURATION = 24 * 60 * 60 * 1000; // 1天冷却(毫秒)
|
||||||
|
private static final int MAX_MESSAGE_LENGTH = 20; // 最大纯文本长度(不含颜色代码)
|
||||||
|
private static final int MESSAGES_PER_PAGE = 10; // 每页留言数
|
||||||
|
|
||||||
|
// 依赖对象
|
||||||
|
private final Survival plugin;
|
||||||
|
private final Gson gson;
|
||||||
|
private final File messagesFile;
|
||||||
|
private final File cooldownsFile;
|
||||||
|
|
||||||
|
// 内存数据缓存
|
||||||
|
private List<Message> messages;
|
||||||
|
private Map<String, PlayerCooldown> playerCooldowns;
|
||||||
|
|
||||||
|
// 留言类型枚举(区分普通留言和上报信息)
|
||||||
|
public enum MessageType {
|
||||||
|
ADD, SUPPORT
|
||||||
|
}
|
||||||
|
|
||||||
|
// 留言实体类(Gson序列化用)
|
||||||
|
public static class Message {
|
||||||
|
private String content; // 带颜色的内容
|
||||||
|
private String playerName; // 玩家名称
|
||||||
|
private String playerUuid; // 玩家UUID(防改名)
|
||||||
|
private long timestamp; // 时间戳
|
||||||
|
private MessageType type; // 留言类型
|
||||||
|
|
||||||
|
// Gson必需的默认构造函数
|
||||||
|
public Message() {}
|
||||||
|
|
||||||
|
public Message(String content, String playerName, String playerUuid, long timestamp, MessageType type) {
|
||||||
|
this.content = content;
|
||||||
|
this.playerName = playerName;
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter和Setter(Gson序列化必需)
|
||||||
|
public String getContent() { return content; }
|
||||||
|
public void setContent(String content) { this.content = content; }
|
||||||
|
public String getPlayerName() { return playerName; }
|
||||||
|
public void setPlayerName(String playerName) { this.playerName = playerName; }
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public long getTimestamp() { return timestamp; }
|
||||||
|
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
|
||||||
|
public MessageType getType() { return type; }
|
||||||
|
public void setType(MessageType type) { this.type = type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 玩家冷却实体类
|
||||||
|
public static class PlayerCooldown {
|
||||||
|
private String playerUuid;
|
||||||
|
private long lastAddTime; // 最后留言时间
|
||||||
|
private long lastSupportTime; // 最后上报时间
|
||||||
|
|
||||||
|
public PlayerCooldown() {}
|
||||||
|
|
||||||
|
public PlayerCooldown(String playerUuid, long lastAddTime, long lastSupportTime) {
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.lastAddTime = lastAddTime;
|
||||||
|
this.lastSupportTime = lastSupportTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter和Setter
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public long getLastAddTime() { return lastAddTime; }
|
||||||
|
public void setLastAddTime(long lastAddTime) { this.lastAddTime = lastAddTime; }
|
||||||
|
public long getLastSupportTime() { return lastSupportTime; }
|
||||||
|
public void setLastSupportTime(long lastSupportTime) { this.lastSupportTime = lastSupportTime; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBoardCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
// 初始化数据文件(存放在插件data目录)
|
||||||
|
this.messagesFile = new File(plugin.getDataFolder(), "messages.json");
|
||||||
|
this.cooldownsFile = new File(plugin.getDataFolder(), "playerCooldowns.json");
|
||||||
|
|
||||||
|
// 加载数据(文件不存在则自动创建)
|
||||||
|
this.messages = loadMessages();
|
||||||
|
this.playerCooldowns = loadPlayerCooldowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 仅允许玩家使用
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "❌ 仅玩家可使用该命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无参数时显示帮助和颜色提示
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendHelpMessage(player);
|
||||||
|
sendColorCodeTips(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子命令
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
switch (subCommand) {
|
||||||
|
case "add" -> handleAddCommand(player, args);
|
||||||
|
case "list" -> handleListCommand(player, args);
|
||||||
|
case "support" -> handleSupportCommand(player, args);
|
||||||
|
default -> {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 未知子命令!可用:add、list、support");
|
||||||
|
sendHelpMessage(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理添加留言命令 /messageboard add <留言>
|
||||||
|
*/
|
||||||
|
private void handleAddCommand(Player player, String[] args) {
|
||||||
|
// 检查参数完整性
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/messageboard add <留言内容>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接留言内容(支持空格)
|
||||||
|
StringBuilder contentSb = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
contentSb.append(args[i]).append(" ");
|
||||||
|
}
|
||||||
|
String rawContent = contentSb.toString().trim();
|
||||||
|
|
||||||
|
// 处理颜色代码(&替换为§)
|
||||||
|
String coloredContent = ChatColor.translateAlternateColorCodes('&', rawContent);
|
||||||
|
|
||||||
|
// 检查纯文本长度(排除颜色代码)
|
||||||
|
int plainLength = getPlainTextLength(rawContent);
|
||||||
|
if (plainLength > MAX_MESSAGE_LENGTH) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 留言过长!纯文本(不含颜色代码)最多" + MAX_MESSAGE_LENGTH + "字符,当前:" + plainLength);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查冷却时间
|
||||||
|
PlayerCooldown cooldown = getOrCreateCooldown(player);
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long remainingTime = cooldown.getLastAddTime() + COOLDOWN_DURATION - currentTime;
|
||||||
|
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 今日留言次数已用尽!请等待" + formatTime(remainingTime) + "后再试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存留言
|
||||||
|
Message message = new Message(
|
||||||
|
coloredContent,
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId().toString(),
|
||||||
|
currentTime,
|
||||||
|
MessageType.ADD
|
||||||
|
);
|
||||||
|
messages.add(message);
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
// 更新冷却时间
|
||||||
|
cooldown.setLastAddTime(currentTime);
|
||||||
|
savePlayerCooldowns();
|
||||||
|
|
||||||
|
// 反馈成功
|
||||||
|
player.sendMessage(ChatColor.GREEN + "✅ 留言发布成功!");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "内容:" + coloredContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理上报命令 /messageboard support <上报信息>
|
||||||
|
*/
|
||||||
|
private void handleSupportCommand(Player player, String[] args) {
|
||||||
|
// 检查参数完整性
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/messageboard support <上报信息>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接上报内容
|
||||||
|
StringBuilder contentSb = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
contentSb.append(args[i]).append(" ");
|
||||||
|
}
|
||||||
|
String rawContent = contentSb.toString().trim();
|
||||||
|
|
||||||
|
// 处理颜色代码
|
||||||
|
String coloredContent = ChatColor.translateAlternateColorCodes('&', rawContent);
|
||||||
|
|
||||||
|
// 检查冷却时间
|
||||||
|
PlayerCooldown cooldown = getOrCreateCooldown(player);
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long remainingTime = cooldown.getLastSupportTime() + COOLDOWN_DURATION - currentTime;
|
||||||
|
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 今日上报次数已用尽!请等待" + formatTime(remainingTime) + "后再试");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存上报信息
|
||||||
|
Message message = new Message(
|
||||||
|
coloredContent,
|
||||||
|
player.getName(),
|
||||||
|
player.getUniqueId().toString(),
|
||||||
|
currentTime,
|
||||||
|
MessageType.SUPPORT
|
||||||
|
);
|
||||||
|
messages.add(message);
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
// 更新冷却时间
|
||||||
|
cooldown.setLastSupportTime(currentTime);
|
||||||
|
savePlayerCooldowns();
|
||||||
|
|
||||||
|
// 反馈成功
|
||||||
|
player.sendMessage(ChatColor.GREEN + "✅ 上报成功!管理员会尽快处理");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "上报内容:" + coloredContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查看列表命令 /messageboard list [页码]
|
||||||
|
*/
|
||||||
|
private void handleListCommand(Player player, String[] args) {
|
||||||
|
// 筛选可见留言(普通玩家仅看ADD,管理员看所有)
|
||||||
|
boolean isAdmin = player.hasPermission("messageboard.admin") || player.isOp();
|
||||||
|
List<Message> visibleMessages = new ArrayList<>();
|
||||||
|
for (Message msg : messages) {
|
||||||
|
if (isAdmin || msg.getType() == MessageType.ADD) {
|
||||||
|
visibleMessages.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间倒序排序(最新在前)
|
||||||
|
visibleMessages.sort(Comparator.comparingLong(Message::getTimestamp).reversed());
|
||||||
|
|
||||||
|
// 解析页码(默认第1页)
|
||||||
|
int page = 1;
|
||||||
|
if (args.length >= 2) {
|
||||||
|
try {
|
||||||
|
page = Integer.parseInt(args[1]);
|
||||||
|
if (page < 1) page = 1;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 页码必须是正整数!默认显示第1页");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页计算
|
||||||
|
int totalMsg = visibleMessages.size();
|
||||||
|
int totalPages = (totalMsg + MESSAGES_PER_PAGE - 1) / MESSAGES_PER_PAGE; // 向上取整
|
||||||
|
|
||||||
|
// 检查页码有效性
|
||||||
|
if (page > totalPages) {
|
||||||
|
player.sendMessage(ChatColor.RED + "❌ 页码超出范围!总页数:" + totalPages);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 截取当前页数据
|
||||||
|
int start = (page - 1) * MESSAGES_PER_PAGE;
|
||||||
|
int end = Math.min(start + MESSAGES_PER_PAGE, totalMsg);
|
||||||
|
List<Message> pageMessages = visibleMessages.subList(start, end);
|
||||||
|
|
||||||
|
// 发送列表信息
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 留言列表(第" + page + "/" + totalPages + "页)=====");
|
||||||
|
if (pageMessages.isEmpty()) {
|
||||||
|
player.sendMessage(ChatColor.GRAY + "📭 当前页暂无留言");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < pageMessages.size(); i++) {
|
||||||
|
Message msg = pageMessages.get(i);
|
||||||
|
String typeTag = msg.getType() == MessageType.SUPPORT ? ChatColor.RED + "[上报] " : "";
|
||||||
|
String line = ChatColor.WHITE + String.format("%d",(start + i + 1)) + ". " + typeTag + msg.getContent()
|
||||||
|
+ ChatColor.GRAY + " - " + msg.getPlayerName();
|
||||||
|
player.sendMessage(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.sendMessage(ChatColor.GREEN + "==========================");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "💡 提示:输入 /messageboard list <页码> 查看其他页");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送帮助信息
|
||||||
|
*/
|
||||||
|
private void sendHelpMessage(Player player) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 留言板命令帮助 =====");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb add <留言> - 发布公开留言(1天1次,纯文本≤20字符)");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb list [页码] - 查看留言列表(1页10条)");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "/mb support <信息> - 上报问题(1天1次,仅管理员可见)");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "==========================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送颜色代码使用提示
|
||||||
|
*/
|
||||||
|
private void sendColorCodeTips(Player player) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "===== 可用颜色代码 =====");
|
||||||
|
player.sendMessage(ChatColor.BLACK + "&0 黑色 " + ChatColor.DARK_BLUE + "&1 深蓝色 " + ChatColor.DARK_GREEN + "&2 深绿色");
|
||||||
|
player.sendMessage(ChatColor.DARK_AQUA + "&3 深青色 " + ChatColor.DARK_RED + "&4 深红色 " + ChatColor.DARK_PURPLE + "&5 深紫色");
|
||||||
|
player.sendMessage(ChatColor.GOLD + "&6 金色 " + ChatColor.GRAY + "&7 灰色 " + ChatColor.DARK_GRAY + "&8 深灰色");
|
||||||
|
player.sendMessage(ChatColor.BLUE + "&9 蓝色 " + ChatColor.GREEN + "&a 绿色 " + ChatColor.AQUA + "&b 青色");
|
||||||
|
player.sendMessage(ChatColor.RED + "&c 红色 " + ChatColor.LIGHT_PURPLE + "&d 紫色 " + ChatColor.YELLOW + "&e 黄色");
|
||||||
|
player.sendMessage(ChatColor.WHITE + "&f 白色");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "=======================");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "💡 提示:颜色代码(&+字符)不计入20字符限制");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算纯文本长度(移除所有颜色代码)
|
||||||
|
*/
|
||||||
|
private int getPlainTextLength(String rawContent) {
|
||||||
|
// 正则替换所有 & 后跟一个字符(颜色代码)为空白
|
||||||
|
return rawContent.replaceAll("&.", "").length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间(毫秒转天时分秒)
|
||||||
|
*/
|
||||||
|
private String formatTime(long ms) {
|
||||||
|
long days = ms / (24 * 60 * 60 * 1000);
|
||||||
|
ms %= 24 * 60 * 60 * 1000;
|
||||||
|
long hours = ms / (60 * 60 * 1000);
|
||||||
|
ms %= 60 * 60 * 1000;
|
||||||
|
long minutes = ms / (60 * 1000);
|
||||||
|
long seconds = ms / 1000;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (days > 0) sb.append(days).append("天");
|
||||||
|
if (hours > 0) sb.append(hours).append("时");
|
||||||
|
if (minutes > 0) sb.append(minutes).append("分");
|
||||||
|
if (seconds > 0) sb.append(seconds).append("秒");
|
||||||
|
return sb.toString().isEmpty() ? "0秒" : sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或创建玩家冷却数据
|
||||||
|
*/
|
||||||
|
private PlayerCooldown getOrCreateCooldown(Player player) {
|
||||||
|
String uuid = player.getUniqueId().toString();
|
||||||
|
return playerCooldowns.computeIfAbsent(uuid, k -> new PlayerCooldown(uuid, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载留言数据(从JSON文件)
|
||||||
|
*/
|
||||||
|
private List<Message> loadMessages() {
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
createFile(messagesFile);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(messagesFile)) {
|
||||||
|
Type listType = new TypeToken<ArrayList<Message>>() {}.getType();
|
||||||
|
List<Message> loaded = gson.fromJson(reader, listType);
|
||||||
|
return loaded != null ? loaded : new ArrayList<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载留言文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存留言数据(到JSON文件)
|
||||||
|
*/
|
||||||
|
private void saveMessages() {
|
||||||
|
try (FileWriter writer = new FileWriter(messagesFile)) {
|
||||||
|
gson.toJson(messages, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存留言文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载玩家冷却数据
|
||||||
|
*/
|
||||||
|
private Map<String, PlayerCooldown> loadPlayerCooldowns() {
|
||||||
|
if (!cooldownsFile.exists()) {
|
||||||
|
createFile(cooldownsFile);
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(cooldownsFile)) {
|
||||||
|
Type mapType = new TypeToken<HashMap<String, PlayerCooldown>>() {}.getType();
|
||||||
|
Map<String, PlayerCooldown> loaded = gson.fromJson(reader, mapType);
|
||||||
|
return loaded != null ? loaded : new HashMap<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载冷却文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存玩家冷却数据
|
||||||
|
*/
|
||||||
|
private void savePlayerCooldowns() {
|
||||||
|
try (FileWriter writer = new FileWriter(cooldownsFile)) {
|
||||||
|
gson.toJson(playerCooldowns, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存冷却文件失败:" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文件(自动创建父目录)
|
||||||
|
*/
|
||||||
|
private void createFile(File file) {
|
||||||
|
try {
|
||||||
|
file.getParentFile().mkdirs(); // 创建父目录
|
||||||
|
file.createNewFile(); // 创建文件
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("创建文件失败:" + file.getPath() + " - " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class NoticeCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private Survival plugin;
|
||||||
|
|
||||||
|
public NoticeCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
Player player = Bukkit.getPlayer(args[0]);
|
||||||
|
if(!player.isOnline()){
|
||||||
|
sender.sendMessage(ChatColor.RED+"玩家不在线");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!player.isOp()){
|
||||||
|
sender.sendMessage(ChatColor.RED+"你无权使用该命令");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
player.sendMessage(ChatColor.RED+"警告"+ChatColor.WHITE+" | "+ChatColor.GRAY+"检测到您有疑似作弊行为,请立即停止。");
|
||||||
|
player.sendTitle(ChatColor.AQUA+"星阁钱语",ChatColor.RED+"您的行为已上报,正在自动检查。",20,160,20);
|
||||||
|
//player.playSound(player, Sound.BLOCK_NOTE_BLOCK_XYLOPHONE,1.0f,1.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PartyCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public PartyCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void colorfulline(Player player) {
|
||||||
|
player.sendMessage(
|
||||||
|
ChatColor.DARK_RED + "---" +
|
||||||
|
ChatColor.RED + "---" +
|
||||||
|
ChatColor.GREEN + "---" +
|
||||||
|
ChatColor.BLUE + "---" +
|
||||||
|
ChatColor.YELLOW + "---" +
|
||||||
|
ChatColor.LIGHT_PURPLE + "---" +
|
||||||
|
ChatColor.AQUA + "---" +
|
||||||
|
ChatColor.LIGHT_PURPLE + "---" +
|
||||||
|
ChatColor.DARK_PURPLE + "---"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player send = (Player) sender;
|
||||||
|
if (args.length < 1) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "参数不足!请输入 /party help 查看命令用法");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "create":
|
||||||
|
handleCreate(send);
|
||||||
|
break;
|
||||||
|
case "join":
|
||||||
|
handleJoin(send, args);
|
||||||
|
break;
|
||||||
|
case "quit":
|
||||||
|
handleQuit(send);
|
||||||
|
break;
|
||||||
|
case "list":
|
||||||
|
handleList(send);
|
||||||
|
break;
|
||||||
|
case "kick":
|
||||||
|
handleKick(send, args);
|
||||||
|
break;
|
||||||
|
case "ban":
|
||||||
|
handleBan(send, args);
|
||||||
|
break;
|
||||||
|
case "disband":
|
||||||
|
handleDisband(send);
|
||||||
|
break;
|
||||||
|
case "help":
|
||||||
|
handleHelp(send);
|
||||||
|
break;
|
||||||
|
case "check":
|
||||||
|
send.sendMessage(ChatColor.RED + "无权使用该命令!");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sender.sendMessage(ChatColor.RED + "参数错误!请输入 /party help 查看命令用法");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建队伍
|
||||||
|
private void handleCreate(Player player) {
|
||||||
|
if (plugin.party.getOrDefault(player, null) != null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "您已经加入了一个队伍!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colorfulline(player);
|
||||||
|
int pid = (int) (Math.random() * 89999 + 10000);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "队伍创建成功,队伍编号: " + pid);
|
||||||
|
plugin.party.put(player, pid);
|
||||||
|
List<Player> playerList = new ArrayList<>();
|
||||||
|
playerList.add(player);
|
||||||
|
plugin.partyp.put(pid, playerList);
|
||||||
|
plugin.owner.put(pid, player);
|
||||||
|
colorfulline(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入队伍
|
||||||
|
private void handleJoin(Player player, String[] args) {
|
||||||
|
// 检查参数长度
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatColor.RED + "用法错误!正确格式:/party join <队伍号>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已加入队伍
|
||||||
|
if (plugin.party.getOrDefault(player, null) != null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "您已经加入了一个队伍!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析队伍ID(处理非数字输入)
|
||||||
|
int pid;
|
||||||
|
try {
|
||||||
|
pid = Integer.parseInt(args[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
player.sendMessage(ChatColor.RED + "队伍编号必须是数字!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查队伍是否存在
|
||||||
|
if (plugin.partyp.getOrDefault(pid, null) == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "队伍不存在!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否被ban
|
||||||
|
List<Player> bannedPlayers = plugin.ban.getOrDefault(pid, new ArrayList<>());
|
||||||
|
if (bannedPlayers.contains(player)) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你已被该队伍禁止加入!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行加入逻辑
|
||||||
|
colorfulline(player);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "加入队伍成功!");
|
||||||
|
colorfulline(player);
|
||||||
|
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
// 通知队伍成员
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
colorfulline(member);
|
||||||
|
member.sendMessage(player.getDisplayName() + ChatColor.GREEN + " 加入了队伍");
|
||||||
|
colorfulline(member);
|
||||||
|
}
|
||||||
|
// 添加到队伍
|
||||||
|
teamPlayers.add(player);
|
||||||
|
plugin.partyp.put(pid, teamPlayers);
|
||||||
|
plugin.party.put(player, pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出队伍
|
||||||
|
private void handleQuit(Player player) {
|
||||||
|
Integer pid = plugin.party.getOrDefault(player, null);
|
||||||
|
if (pid == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "您尚未加入队伍!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
colorfulline(player);
|
||||||
|
player.sendMessage(ChatColor.RED + "退出队伍成功!");
|
||||||
|
colorfulline(player);
|
||||||
|
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
// 通知队伍成员
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
colorfulline(member);
|
||||||
|
member.sendMessage(player.getDisplayName() + ChatColor.RED + " 退出了队伍");
|
||||||
|
colorfulline(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除玩家
|
||||||
|
teamPlayers.remove(player);
|
||||||
|
// 如果是队长退出,解散队伍
|
||||||
|
if (plugin.owner.getOrDefault(pid, null).equals(player)) {
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
colorfulline(member);
|
||||||
|
member.sendMessage(ChatColor.RED + "队伍已解散");
|
||||||
|
colorfulline(member);
|
||||||
|
plugin.party.remove(member);
|
||||||
|
}
|
||||||
|
plugin.owner.remove(pid);
|
||||||
|
plugin.partyp.remove(pid);
|
||||||
|
plugin.ban.remove(pid);
|
||||||
|
} else {
|
||||||
|
plugin.partyp.put(pid, teamPlayers);
|
||||||
|
}
|
||||||
|
plugin.party.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 队伍列表
|
||||||
|
private void handleList(Player player) {
|
||||||
|
Integer pid = plugin.party.getOrDefault(player, null);
|
||||||
|
if (pid == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "您还未加入队伍");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
colorfulline(player);
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
boolean isOwner = plugin.owner.getOrDefault(pid, null).equals(player);
|
||||||
|
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
ComponentBuilder builder = new ComponentBuilder(member.getDisplayName());
|
||||||
|
// 公共传送按钮
|
||||||
|
builder.append(ChatColor.GREEN + " [传送到] ")
|
||||||
|
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/teleport " + member.getName()));
|
||||||
|
builder.append(ChatColor.GREEN + "[传送来] ")
|
||||||
|
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/teleport " + member.getName() + " me"));
|
||||||
|
// 队长额外的踢出按钮
|
||||||
|
if (isOwner) {
|
||||||
|
builder.append(ChatColor.RED + "[踢出]")
|
||||||
|
.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/party kick " + member.getName()));
|
||||||
|
}
|
||||||
|
player.spigot().sendMessage(builder.create());
|
||||||
|
}
|
||||||
|
colorfulline(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 踢出玩家
|
||||||
|
private void handleKick(Player sender, String[] args) {
|
||||||
|
// 检查参数长度
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/party kick <玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer pid = plugin.party.getOrDefault(sender, null);
|
||||||
|
if (pid == null) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "您还未加入队伍");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为队长
|
||||||
|
if (!plugin.owner.getOrDefault(pid, null).equals(sender)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "你无权使用该命令!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标玩家
|
||||||
|
Player target = Bukkit.getPlayer(args[1]);
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "玩家不在线或不存在!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
// 修正:判断目标是否在队伍内(原逻辑颠倒)
|
||||||
|
if (!teamPlayers.contains(target)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "队伍内无该玩家!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行踢出逻辑
|
||||||
|
colorfulline(sender);
|
||||||
|
// 通知被踢出玩家
|
||||||
|
colorfulline(target);
|
||||||
|
target.sendMessage(ChatColor.RED + "您已被踢出队伍!");
|
||||||
|
colorfulline(target);
|
||||||
|
|
||||||
|
// 更新数据
|
||||||
|
plugin.party.remove(target);
|
||||||
|
teamPlayers.remove(target);
|
||||||
|
plugin.partyp.put(pid, teamPlayers);
|
||||||
|
|
||||||
|
// 通知其他成员
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
colorfulline(member);
|
||||||
|
member.sendMessage(target.getDisplayName() + ChatColor.RED + " 已被踢出队伍");
|
||||||
|
colorfulline(member);
|
||||||
|
}
|
||||||
|
colorfulline(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁止玩家加入
|
||||||
|
private void handleBan(Player sender, String[] args) {
|
||||||
|
// 检查参数长度
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/party ban <玩家名>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer pid = plugin.party.getOrDefault(sender, null);
|
||||||
|
if (pid == null) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "您还未加入队伍");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为队长
|
||||||
|
if (!plugin.owner.getOrDefault(pid, null).equals(sender)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "你无权使用该命令!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标玩家
|
||||||
|
Player target = Bukkit.getPlayer(args[1]);
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "玩家不在线或不存在!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
// 修正:判断目标是否在队伍内(原逻辑颠倒)
|
||||||
|
if (teamPlayers.contains(target)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无法禁止队伍内的玩家!请先使用kick命令踢出");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化ban列表(避免空指针)
|
||||||
|
List<Player> bannedPlayers = plugin.ban.getOrDefault(pid, new ArrayList<>());
|
||||||
|
if (bannedPlayers.contains(target)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "该玩家已被禁止加入!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行ban逻辑
|
||||||
|
colorfulline(sender);
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "成功禁止 " + target.getDisplayName() + " 加入队伍!");
|
||||||
|
colorfulline(sender);
|
||||||
|
|
||||||
|
// 通知目标玩家
|
||||||
|
colorfulline(target);
|
||||||
|
target.sendMessage(ChatColor.RED + "您已被 " + sender.getDisplayName() + " 的队伍禁止加入!");
|
||||||
|
colorfulline(target);
|
||||||
|
|
||||||
|
// 更新ban列表
|
||||||
|
bannedPlayers.add(target);
|
||||||
|
plugin.ban.put(pid, bannedPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解散队伍
|
||||||
|
private void handleDisband(Player sender) {
|
||||||
|
Integer pid = plugin.party.getOrDefault(sender, null);
|
||||||
|
if (pid == null) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "您还未加入队伍");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为队长
|
||||||
|
if (!plugin.owner.getOrDefault(pid, null).equals(sender)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "你无权使用该命令!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> teamPlayers = plugin.partyp.get(pid);
|
||||||
|
// 先通知所有成员,再移除数据(原逻辑先移除导致通知不全)
|
||||||
|
colorfulline(sender);
|
||||||
|
sender.sendMessage(ChatColor.RED + "队伍解散成功");
|
||||||
|
colorfulline(sender);
|
||||||
|
|
||||||
|
for (Player member : teamPlayers) {
|
||||||
|
if (!member.equals(sender)) {
|
||||||
|
colorfulline(member);
|
||||||
|
member.sendMessage(ChatColor.RED + "队伍已解散");
|
||||||
|
colorfulline(member);
|
||||||
|
plugin.party.remove(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理队伍数据
|
||||||
|
plugin.owner.remove(pid);
|
||||||
|
plugin.party.remove(sender);
|
||||||
|
plugin.partyp.remove(pid);
|
||||||
|
plugin.ban.remove(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帮助信息
|
||||||
|
private void handleHelp(Player player) {
|
||||||
|
colorfulline(player);
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party join <队伍号> - 加入指定队伍");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party quit - 退出当前队伍");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party disband - 解散当前队伍(仅队长)");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party create - 创建新队伍");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party ban <玩家名> - 禁止指定玩家加入(仅队长)");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party kick <玩家名> - 踢出队伍成员(仅队长)");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party list - 查看队伍成员及快捷功能");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/party help - 查看命令帮助");
|
||||||
|
player.sendMessage(ChatColor.GREEN + "/pc <消息> - 队伍内讲话");
|
||||||
|
colorfulline(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PcCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public PcCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player send = (Player) sender;
|
||||||
|
// 检查是否加入队伍
|
||||||
|
Integer partyId = plugin.party.get(send);
|
||||||
|
if (partyId == null) {
|
||||||
|
send.sendMessage(ChatColor.RED + "您尚未加入队伍!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接消息内容(处理空格分割的参数)
|
||||||
|
String originalMessage = String.join(" ", args);
|
||||||
|
|
||||||
|
// 检查空消息(去除首尾空格后为空)
|
||||||
|
if (originalMessage.trim().isEmpty()) {
|
||||||
|
send.sendMessage(ChatColor.RED + "不允许发送空消息!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制消息长度为32字符内(中文算1个字符)
|
||||||
|
String processedMessage;
|
||||||
|
if (originalMessage.length() > 32) {
|
||||||
|
processedMessage = originalMessage.substring(0, 32); // 截取前32个字符
|
||||||
|
} else {
|
||||||
|
processedMessage = originalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送给队伍所有成员
|
||||||
|
List<Player> teamMembers = plugin.partyp.get(partyId);
|
||||||
|
for (Player member : teamMembers) {
|
||||||
|
member.sendMessage(
|
||||||
|
ChatColor.BLUE + "队伍" +
|
||||||
|
ChatColor.WHITE + send.getName() + " > " +
|
||||||
|
processedMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class PointCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private Survival plugin;
|
||||||
|
|
||||||
|
public PointCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if(args.length != 4){
|
||||||
|
sender.sendMessage(ChatColor.RED+"参数不足或参数过多!\n/point <player> <add|remove|set> <number> <reason>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Player player = Bukkit.getPlayer(args[0]);
|
||||||
|
if(!player.isOnline()){
|
||||||
|
sender.sendMessage(ChatColor.RED+"玩家不存在!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(args[1].equals("add")){
|
||||||
|
plugin.ppoint.put(player,plugin.ppoint.get(player)+Integer.parseInt(args[2]));
|
||||||
|
plugin.breason.put(player,args[3]);
|
||||||
|
player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2]));
|
||||||
|
}else if(args[1].equals("set")){
|
||||||
|
plugin.breason.put(player,args[3]);
|
||||||
|
if(Integer.parseInt(args[2]) < plugin.ppoint.get(player))player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2]));
|
||||||
|
plugin.ppoint.put(player,Integer.parseInt(args[2]));
|
||||||
|
}else if(args[1].equals("remove")){
|
||||||
|
plugin.ppoint.put(player,plugin.ppoint.get(player)-Integer.parseInt(args[2]));
|
||||||
|
plugin.breason.put(player,args[3]);
|
||||||
|
player.getScoreboard().getObjective("handled").getScore(player).setScore(player.getScoreboard().getObjective(("handled")).getScore(player).getScore()+Integer.parseInt(args[2]));
|
||||||
|
}else{
|
||||||
|
sender.sendMessage(ChatColor.RED+"参数错误!\n/point <player> <add|remove|set> <number> <reason>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ public class PvpCommandExecutor implements CommandExecutor {
|
|||||||
if (plugin.krt.getOrDefault(sender, false) == true) {
|
if (plugin.krt.getOrDefault(sender, false) == true) {
|
||||||
plugin.krt.put((Player) sender, false);
|
plugin.krt.put((Player) sender, false);
|
||||||
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 关");
|
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 关");
|
||||||
|
//
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 开");
|
sender.sendMessage(ChatColor.GREEN + "已切换PVP模式到: 开");
|
||||||
plugin.krt.put((Player) sender, true);
|
plugin.krt.put((Player) sender, true);
|
||||||
|
|||||||
237
src/main/java/org/xgqy/survival/command/QdCommandExecutor.java
Normal file
237
src/main/java/org/xgqy/survival/command/QdCommandExecutor.java
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class QdCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
public QdCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "此命令仅玩家可使用!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
// 创建54格签到界面(6行×9列)
|
||||||
|
Inventory signInGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "每日签到奖励");
|
||||||
|
|
||||||
|
// 初始化背景物品(灰色玻璃 pane)
|
||||||
|
ItemStack background = createBackgroundItem();
|
||||||
|
fillBackground(signInGui, background);
|
||||||
|
|
||||||
|
// 设置金币奖励(1,3,5,7,9,11,13位置 - 对应索引0,2,4,6,8,10,12)
|
||||||
|
setupGoldRewards(signInGui);
|
||||||
|
|
||||||
|
// 设置礼包奖励(8,14位置 - 对应索引7,13)
|
||||||
|
setupGiftRewards(signInGui);
|
||||||
|
|
||||||
|
// 设置矿石奖励(2,12位置 - 对应索引1,11)
|
||||||
|
setupOreRewards(signInGui);
|
||||||
|
|
||||||
|
// 设置点券奖励(4,10位置 - 对应索引3,9)
|
||||||
|
setupPointRewards(signInGui);
|
||||||
|
|
||||||
|
// 设置高级附魔书奖励(6位置 - 对应索引5)
|
||||||
|
setupEnchantBookReward(signInGui);
|
||||||
|
|
||||||
|
// 打开签到界面
|
||||||
|
player.openInventory(signInGui);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建背景装饰物品
|
||||||
|
*/
|
||||||
|
private ItemStack createBackgroundItem() {
|
||||||
|
ItemStack background = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||||
|
ItemMeta meta = background.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(" "); // 空白名称
|
||||||
|
background.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return background;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充背景物品
|
||||||
|
*/
|
||||||
|
private void fillBackground(Inventory inventory, ItemStack background) {
|
||||||
|
for (int i = 0; i < inventory.getSize(); i++) {
|
||||||
|
inventory.setItem(i, background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置金币奖励物品
|
||||||
|
*/
|
||||||
|
private void setupGoldRewards(Inventory gui) {
|
||||||
|
// 金币奖励位置(索引)与对应金额
|
||||||
|
int[] slots = {0, 2, 4, 6, 8, 10, 12};
|
||||||
|
int[] amounts = {200, 500, 1000, 1500, 2000, 5000, 10000};
|
||||||
|
String[] days = {"第1天", "第2天", "第3天", "第4天", "第5天", "第6天", "第7天(累计)"};
|
||||||
|
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
ItemStack gold = new ItemStack(Material.GOLD_INGOT);
|
||||||
|
ItemMeta meta = gold.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ChatColor.YELLOW + "金币奖励 " + days[i]);
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.YELLOW + amounts[i] + " 金币");
|
||||||
|
lore.add(ChatColor.GRAY + "点击领取奖励");
|
||||||
|
meta.setLore(lore);
|
||||||
|
gold.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
gui.setItem(slots[i], gold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置礼包奖励物品
|
||||||
|
*/
|
||||||
|
private void setupGiftRewards(Inventory gui) {
|
||||||
|
int[] slots = {7, 13};
|
||||||
|
String[] giftNames = {"普通签到礼包", "高级签到礼包"};
|
||||||
|
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
ItemStack gift = new ItemStack(Material.ENDER_CHEST);
|
||||||
|
ItemMeta meta = gift.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ChatColor.GREEN + giftNames[i]);
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ChatColor.GRAY + "包含随机珍贵物品");
|
||||||
|
lore.add(ChatColor.GRAY + "内含材料、工具或特殊道具");
|
||||||
|
lore.add(ChatColor.GRAY + "点击领取");
|
||||||
|
meta.setLore(lore);
|
||||||
|
gift.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
gui.setItem(slots[i], gift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置矿石奖励物品
|
||||||
|
*/
|
||||||
|
private void setupOreRewards(Inventory gui) {
|
||||||
|
int[] slots = {1, 11};
|
||||||
|
Material[] ores = {Material.DIAMOND, Material.IRON_BLOCK};
|
||||||
|
String[] oreNames = {"钻石矿石", "铁矿块"};
|
||||||
|
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
ItemStack ore = new ItemStack(ores[i]);
|
||||||
|
ItemMeta meta = ore.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ChatColor.AQUA + oreNames[i] + "奖励");
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.AQUA + oreNames[i] + " x5");
|
||||||
|
lore.add(ChatColor.GRAY + "点击领取");
|
||||||
|
meta.setLore(lore);
|
||||||
|
ore.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
gui.setItem(slots[i], ore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置点券奖励物品
|
||||||
|
*/
|
||||||
|
private void setupPointRewards(Inventory gui) {
|
||||||
|
int[] slots = {3, 9};
|
||||||
|
int[] amounts = {50, 100};
|
||||||
|
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
ItemStack point = new ItemStack(Material.EMERALD);
|
||||||
|
ItemMeta meta = point.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(ChatColor.GREEN + "点券奖励");
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.GREEN + amounts[i] + " 点券");
|
||||||
|
lore.add(ChatColor.GRAY + "可用于兑换特殊道具");
|
||||||
|
lore.add(ChatColor.GRAY + "点击领取");
|
||||||
|
meta.setLore(lore);
|
||||||
|
point.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
gui.setItem(slots[i], point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置高级附魔书奖励
|
||||||
|
*/
|
||||||
|
private void setupEnchantBookReward(Inventory gui) {
|
||||||
|
ItemStack enchBook = new ItemStack(Material.ENCHANTED_BOOK);
|
||||||
|
EnchantmentStorageMeta bookMeta = (EnchantmentStorageMeta) enchBook.getItemMeta();
|
||||||
|
|
||||||
|
if (bookMeta != null) {
|
||||||
|
bookMeta.setDisplayName(ChatColor.LIGHT_PURPLE + "高级附魔书");
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(ChatColor.GRAY + "包含以下高级附魔:");
|
||||||
|
|
||||||
|
// 添加指定高级附魔(生存最大等级+2~5)
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.SHARPNESS); // 锋利
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.PROTECTION); // 保护
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.DEPTH_STRIDER); // 亡灵杀手
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.BANE_OF_ARTHROPODS);// 截肢杀手
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.THORNS); // 荆棘
|
||||||
|
addEnchantment(bookMeta, lore, Enchantment.EFFICIENCY); // 耐久
|
||||||
|
|
||||||
|
lore.add("");
|
||||||
|
lore.add(ChatColor.GRAY + "点击领取此附魔书");
|
||||||
|
bookMeta.setLore(lore);
|
||||||
|
enchBook.setItemMeta(bookMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.setItem(5, enchBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为附魔书添加指定附魔并设置等级
|
||||||
|
*/
|
||||||
|
private void addEnchantment(EnchantmentStorageMeta meta, List<String> lore, Enchantment enchantment) {
|
||||||
|
if (enchantment == null) return;
|
||||||
|
|
||||||
|
int maxLevel = enchantment.getMaxLevel();
|
||||||
|
int customLevel = maxLevel + random.nextInt(4) + 2; // +2到5级
|
||||||
|
meta.addStoredEnchant(enchantment, customLevel, true);
|
||||||
|
|
||||||
|
// 获取附魔显示名称
|
||||||
|
String enchName = getEnchantmentName(enchantment);
|
||||||
|
lore.add(ChatColor.GRAY + "- " + enchName + " " + customLevel + "级");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取附魔的中文显示名称
|
||||||
|
*/
|
||||||
|
private String getEnchantmentName(Enchantment enchantment) {
|
||||||
|
if (enchantment.equals(Enchantment.SHARPNESS)) return "锋利";
|
||||||
|
if (enchantment.equals(Enchantment.PROTECTION)) return "保护";
|
||||||
|
if (enchantment.equals(Enchantment.DEPTH_STRIDER)) return "亡灵杀手";
|
||||||
|
if (enchantment.equals(Enchantment.BANE_OF_ARTHROPODS)) return "截肢杀手";
|
||||||
|
if (enchantment.equals(Enchantment.THORNS)) return "荆棘";
|
||||||
|
if (enchantment.equals(Enchantment.EFFICIENCY)) return "耐久";
|
||||||
|
return enchantment.getKey().getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
216
src/main/java/org/xgqy/survival/command/RankCommandExecutor.java
Normal file
216
src/main/java/org/xgqy/survival/command/RankCommandExecutor.java
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
import org.xgqy.survival.event.RankAdd;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RankCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 关键修正1:成员变量类型改为 ContributionManager(之前错写为 RankAdd)
|
||||||
|
private final RankAdd contributionManager;
|
||||||
|
private static final String ADMIN_PERMISSION = "rank.admin";
|
||||||
|
private static final int TOP_LIMIT = 10;
|
||||||
|
|
||||||
|
public RankCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// 关键修正2:获取正确的 ContributionManager 实例(依赖主类的 getContributionManager 方法)
|
||||||
|
this.contributionManager = plugin.getContributionManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendTop10Rank(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
switch (subCommand) {
|
||||||
|
case "add":
|
||||||
|
handleAddScore(sender, args);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
handleRemoveScore(sender, args);
|
||||||
|
break;
|
||||||
|
case "all":
|
||||||
|
sendAllRank(sender);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sender.sendMessage(ChatColor.RED + "未知子命令!可用:add、remove、all");
|
||||||
|
sendHelpMessage(sender);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private void sendTop10Rank(CommandSender sender) {
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "===== 贡献值排行榜(前10名)=====");
|
||||||
|
// 关键修正3:使用 ContributionManager 的正确内部类 PlayerContribution
|
||||||
|
List<RankAdd.PlayerContribution> topList = contributionManager.getTopContributors();
|
||||||
|
|
||||||
|
for (int i = 0; i < TOP_LIMIT; i++) {
|
||||||
|
int rank = i + 1;
|
||||||
|
if (i < topList.size()) {
|
||||||
|
RankAdd.PlayerContribution data = topList.get(i);
|
||||||
|
String rankPrefix = switch (rank) {
|
||||||
|
case 1 -> ChatColor.RED + "🥇 " + ChatColor.WHITE;
|
||||||
|
case 2 -> ChatColor.GRAY + "🥈 " + ChatColor.WHITE;
|
||||||
|
case 3 -> ChatColor.YELLOW + "🥉 " + ChatColor.WHITE;
|
||||||
|
default -> ChatColor.WHITE + "[" + rank + "] ";
|
||||||
|
};
|
||||||
|
// 修正4:现在能正确识别 getPlayerName() 和 getTotalContribution()(因为 data 是正确的类型)
|
||||||
|
String line = rankPrefix + data.getPlayerName() + ChatColor.GRAY + " - " + ChatColor.YELLOW + data.getTotalContribution() + "点";
|
||||||
|
sender.sendMessage(line);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "[" + rank + "] " + ChatColor.GRAY + "暂无 - 暂无");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "==============================");
|
||||||
|
if (sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "管理员提示:使用 /rank all 查看完整列表");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送所有玩家贡献值完整列表
|
||||||
|
*/
|
||||||
|
private void sendAllRank(CommandSender sender) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可查看完整列表");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "===== 全服贡献值完整列表 =====");
|
||||||
|
List<RankAdd.PlayerContribution> allList = contributionManager.getAllContributions();
|
||||||
|
|
||||||
|
if (allList.isEmpty()) {
|
||||||
|
sender.sendMessage(ChatColor.GRAY + "暂无任何玩家贡献值数据");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < allList.size(); i++) {
|
||||||
|
RankAdd.PlayerContribution data = allList.get(i);
|
||||||
|
int rank = i + 1;
|
||||||
|
String line = ChatColor.WHITE + "[" + rank + "] " + data.getPlayerName() +
|
||||||
|
ChatColor.GRAY + " - " + ChatColor.YELLOW + data.getTotalContribution() + "点";
|
||||||
|
sender.sendMessage(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "==============================");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "总计 " + allList.size() + " 名玩家有贡献值记录");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员添加玩家贡献值
|
||||||
|
*/
|
||||||
|
private void handleAddScore(CommandSender sender, String[] args) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可操作");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/rank add <玩家名> <分数>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = args[1];
|
||||||
|
int score;
|
||||||
|
try {
|
||||||
|
score = Integer.parseInt(args[2]);
|
||||||
|
if (score <= 0) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须为正整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须是整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "未找到玩家 " + playerName + "(从未加入过服务器)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = "管理员[" + sender.getName() + "]手动添加分数";
|
||||||
|
// 修正5:调用 RankAdd 的正确方法,内部类枚举也需正确引用
|
||||||
|
int newScore = contributionManager.addCustomContribution(
|
||||||
|
targetPlayer.getPlayer(),
|
||||||
|
score,
|
||||||
|
RankAdd.ContributionType.TASK_MAIN, // 正确引用枚举
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "操作成功!");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "玩家:" + targetPlayer.getName());
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "添加分数:" + ChatColor.YELLOW + score + "点");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "当前总贡献:" + ChatColor.YELLOW + newScore + "点");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员减少玩家贡献值
|
||||||
|
*/
|
||||||
|
private void handleRemoveScore(CommandSender sender, String[] args) {
|
||||||
|
if (!sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无权限使用该命令!仅管理员可操作");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/rank remove <玩家名> <分数>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = args[1];
|
||||||
|
int score;
|
||||||
|
try {
|
||||||
|
score = Integer.parseInt(args[2]);
|
||||||
|
if (score <= 0) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须为正整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "分数必须是整数!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!targetPlayer.hasPlayedBefore() && !targetPlayer.isOnline()) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "未找到玩家 " + playerName + "(从未加入过服务器)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String reason = "管理员[" + sender.getName() + "]手动扣除分数";
|
||||||
|
int newScore = contributionManager.reduceContribution(
|
||||||
|
targetPlayer.getPlayer(),
|
||||||
|
score,
|
||||||
|
RankAdd.ContributionType.TASK_MAIN,
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "操作成功!");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "玩家:" + targetPlayer.getName());
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "扣除分数:" + ChatColor.RED + score + "点");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "当前总贡献:" + ChatColor.YELLOW + newScore + "点");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送帮助信息
|
||||||
|
*/
|
||||||
|
private void sendHelpMessage(CommandSender sender) {
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "===== 贡献值排行榜命令帮助 =====");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank - 查看前10名贡献值排行榜");
|
||||||
|
if (sender.hasPermission(ADMIN_PERMISSION)) {
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank add <玩家名> <分数> - 为玩家添加贡献值");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank remove <玩家名> <分数> - 为玩家扣除贡献值");
|
||||||
|
sender.sendMessage(ChatColor.WHITE + "/rank all - 查看全服玩家贡献值完整列表");
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "==============================");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class RegCommandExecutor implements CommandExecutor {
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public RegCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "只有玩家可以使用此命令!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
// 检查玩家是否已登录
|
||||||
|
if (plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你已经登录了!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查参数是否正确
|
||||||
|
if (args.length != 3) {
|
||||||
|
player.sendMessage(ChatColor.RED + "用法: /reg <密码> <确认密码> <确认码>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查玩家是否已注册
|
||||||
|
if (plugin.getAccountManager().isRegistered(player.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatColor.RED + "你已经注册过了! 请使用 /login 命令登录");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查两次输入的密码是否一致
|
||||||
|
if (!args[0].equals(args[1])) {
|
||||||
|
player.sendMessage(ChatColor.RED + "两次输入的密码不一致!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查密码长度
|
||||||
|
if (args[0].length() < 6) {
|
||||||
|
player.sendMessage(ChatColor.RED + "密码长度不能少于6个字符!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!args[2].equals("3C11CFE63ED00BD6BC50729CD3D5F393F4AA84B0")){
|
||||||
|
player.kickPlayer(ChatColor.RED+"确认码错误,请仔细阅读!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 注册账号
|
||||||
|
if (plugin.getAccountManager().registerAccount(player, args[0])) {
|
||||||
|
player.sendMessage(ChatColor.GREEN + "注册成功! 请使用 /login <密码> 登录");
|
||||||
|
player.kickPlayer(ChatColor.GREEN+"注册成功,请重进登录!");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatColor.RED + "注册失败! 请稍后再试");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.xgqy.survival.command;
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||||
import org.bukkit.BanList;
|
import org.bukkit.BanList;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
@@ -317,7 +320,18 @@ public class ReportCommandExecutor implements CommandExecutor, Listener {
|
|||||||
ChatColor.GREEN + "举报原因: " + ChatColor.YELLOW + reason + "\n" +
|
ChatColor.GREEN + "举报原因: " + ChatColor.YELLOW + reason + "\n" +
|
||||||
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
ChatColor.YELLOW + "请尽快处理该举报");
|
ChatColor.YELLOW + "请尽快处理该举报");
|
||||||
|
BaseComponent[] message = new ComponentBuilder(ChatColor.YELLOW+"工具栏: ")
|
||||||
|
.append(new ComponentBuilder(ChatColor.GREEN + "[传送到玩家] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tp "+target.getName())).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.GREEN + "[提醒该玩家] \n").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/notice "+target.getName())).create())
|
||||||
|
.append(ChatColor.YELLOW+"处理栏: ")
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[驳回]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle no")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[扣除信用分]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle point")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[封禁1天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 1")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[封禁3天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 3")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[封禁7天]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle 7")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[自定义封禁]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/handle ban")).create())
|
||||||
|
.create();
|
||||||
|
admin.spigot().sendMessage(message);
|
||||||
// 添加到待处理列表
|
// 添加到待处理列表
|
||||||
plugin.banlist.put(admin, target);
|
plugin.banlist.put(admin, target);
|
||||||
plugin.banreason.put(target, reason);
|
plugin.banreason.put(target, reason);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class TeleportCommandExecutor implements CommandExecutor {
|
|||||||
sender.sendMessage(ChatColor.RED + "传送正在冷却,剩余: " + (4 - (System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000 / 60) + " 分 " + (60 - ((System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000) % 60) + " 秒 ");
|
sender.sendMessage(ChatColor.RED + "传送正在冷却,剩余: " + (4 - (System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000 / 60) + " 分 " + (60 - ((System.currentTimeMillis() - plugin.lasttp.getOrDefault(sender, 0L)) / 1000) % 60) + " 秒 ");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (args.length != 1) {
|
if (args.length != 1 && args.length != 2) {
|
||||||
sender.sendMessage(ChatColor.RED + "参数错误!用法: /teleport <玩家名>");
|
sender.sendMessage(ChatColor.RED + "参数错误!用法: /teleport <玩家名>");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -37,6 +37,41 @@ public class TeleportCommandExecutor implements CommandExecutor {
|
|||||||
sender.sendMessage(ChatColor.RED + "该玩家不在线!");
|
sender.sendMessage(ChatColor.RED + "该玩家不在线!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (args.length == 2) {
|
||||||
|
if (args[1].contains("me")) {
|
||||||
|
playerto = Bukkit.getPlayer(args[0]);
|
||||||
|
if (plugin.teleport.getOrDefault(playerto, null) != null) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "对方已经发送了一个请求,请等待");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (plugin.Ateleport.getOrDefault(sender, null) != null) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "你还有一个未处理的请求!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "请求已发送");
|
||||||
|
playerto.sendMessage(ChatColor.GREEN + "玩家 " + sender.getName() + ChatColor.GREEN + " 向你发来传送请求,输入 /tpacc tome 同意这个请求或 /tpacc detome 来拒绝这个请求,有效期2分钟。");
|
||||||
|
BaseComponent[] message = new ComponentBuilder(ChatColor.YELLOW + "玩家 " + sender.getName() + ChatColor.YELLOW + " 向你发来传送请求\n")
|
||||||
|
.append(new ComponentBuilder(ChatColor.GREEN + "[同意] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tpacc tome")).create())
|
||||||
|
.append(new ComponentBuilder(ChatColor.RED + "[拒绝]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tpacc detome")).create())
|
||||||
|
.create();
|
||||||
|
playerto.spigot().sendMessage(message);
|
||||||
|
plugin.Ateleport.put((Player) sender, playerto);
|
||||||
|
plugin.teleport.put(playerto, (Player) sender);
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (plugin.teleportp.getOrDefault(playerto, null) == null)
|
||||||
|
playerto.sendMessage(ChatColor.RED + "玩家 " + sender.getName() + ChatColor.RED + " 的传送请求已过期");
|
||||||
|
if (plugin.teleportp.getOrDefault(playerto, null) == null)
|
||||||
|
sender.sendMessage(ChatColor.RED + "你向 " + playerto.getName() + ChatColor.RED + " 发送的传送请求已过期");
|
||||||
|
plugin.Ateleport.remove((Player) sender);
|
||||||
|
plugin.teleport.remove(playerto);
|
||||||
|
plugin.isteleport.remove(playerto);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, 40 * 60);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
playerto = Bukkit.getPlayer(args[0]);
|
playerto = Bukkit.getPlayer(args[0]);
|
||||||
if (plugin.teleport.getOrDefault((Player) sender, null) != null) {
|
if (plugin.teleport.getOrDefault((Player) sender, null) != null) {
|
||||||
playerto.sendMessage(ChatColor.RED + "您已经向 " + plugin.teleport.get((Player) sender).getName() + ChatColor.RED + " 发送了一份请求");
|
playerto.sendMessage(ChatColor.RED + "您已经向 " + plugin.teleport.get((Player) sender).getName() + ChatColor.RED + " 发送了一份请求");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.xgqy.survival.Survival;
|
|||||||
|
|
||||||
public class TpAccCommandExecutor implements CommandExecutor {
|
public class TpAccCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
private Survival plugin;
|
private final Survival plugin;
|
||||||
|
|
||||||
public TpAccCommandExecutor(Survival plugin) {
|
public TpAccCommandExecutor(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@@ -19,48 +19,159 @@ public class TpAccCommandExecutor implements CommandExecutor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
if (sender instanceof Player) {
|
// 仅允许玩家执行命令
|
||||||
if (plugin.Ateleport.getOrDefault(sender, null) != null) {
|
if (!(sender instanceof Player player)) {
|
||||||
if (args[0].contains("accept")) {
|
sender.sendMessage(ChatColor.RED + "无法对非玩家使用此命令");
|
||||||
sender.sendMessage(ChatColor.GREEN + "传送成功!");
|
|
||||||
plugin.Ateleport.get(sender).sendMessage(ChatColor.GREEN + "传送成功!输入 /tpfin 回到原处,该命令将会在5分钟后过期。");
|
|
||||||
plugin.teleportp.put(plugin.Ateleport.get(sender), plugin.Ateleport.get(sender).getLocation());
|
|
||||||
plugin.Ateleport.get(sender).teleport(((Player) sender).getLocation());
|
|
||||||
plugin.isteleport.put(plugin.Ateleport.get(sender), 1);
|
|
||||||
new BukkitRunnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (plugin.isteleport.get(sender) != 1)
|
|
||||||
plugin.Ateleport.get(sender).sendMessage(ChatColor.RED + "返回命令已过期,你将无法返回原处!");
|
|
||||||
plugin.teleportp.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.teleport.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.isteleport.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.Ateleport.remove(sender);
|
|
||||||
}
|
|
||||||
}.runTaskLater(plugin, 5 * 20 * 60);
|
|
||||||
return true;
|
|
||||||
} else if (args[0].contains("deny")) {
|
|
||||||
sender.sendMessage(ChatColor.GREEN + "拒绝成功");
|
|
||||||
plugin.Ateleport.get(sender).sendMessage(ChatColor.GREEN + "您的传送请求被拒绝");
|
|
||||||
//plugin.teleportp.put(plugin.Ateleport.get(sender),plugin.Ateleport.get(sender).getLocation());
|
|
||||||
plugin.teleportp.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.teleport.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.isteleport.remove(plugin.Ateleport.get(sender));
|
|
||||||
plugin.Ateleport.remove(sender);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(ChatColor.RED + "参数错误,请输入 /tpacc accept 或 /tpacc deny");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(ChatColor.RED + "现在没有传送申请或申请已过期");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//return true;
|
|
||||||
|
// 检查玩家是否已登录
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
player.sendMessage(ChatColor.RED + "请先登录或注册!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数长度校验(必须为1个参数)
|
||||||
|
if (args.length != 1) {
|
||||||
|
player.sendMessage(ChatColor.RED + "请输入正确参数!用法: /tpacc <accept|deny|tome|detome>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理不同参数逻辑
|
||||||
|
String action = args[0];
|
||||||
|
switch (action) {
|
||||||
|
case "accept":
|
||||||
|
handleAccept(player);
|
||||||
|
break;
|
||||||
|
case "deny":
|
||||||
|
handleDeny(player);
|
||||||
|
break;
|
||||||
|
case "tome":
|
||||||
|
handleTome(player);
|
||||||
|
break;
|
||||||
|
case "detome":
|
||||||
|
handleDetome(player);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
player.sendMessage(ChatColor.RED + "参数错误!请使用 /tpacc <accept|deny|tome|detome>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理接受对方传送至自己的请求
|
||||||
|
*/
|
||||||
|
private void handleAccept(Player receiver) {
|
||||||
|
// 获取请求传送的玩家(Ateleport: 接收者 -> 请求者)
|
||||||
|
Player requester = plugin.Ateleport.get(receiver);
|
||||||
|
if (requester == null || !requester.isOnline()) {
|
||||||
|
receiver.sendMessage(ChatColor.RED + "传送请求已过期或不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行传送逻辑
|
||||||
|
receiver.sendMessage(ChatColor.GREEN + "已同意传送请求!");
|
||||||
|
requester.sendMessage(ChatColor.GREEN + "对方已同意传送!输入 /tpfin 可在5分钟内返回原处");
|
||||||
|
|
||||||
|
// 保存请求者原始位置
|
||||||
|
plugin.teleportp.put(requester, requester.getLocation());
|
||||||
|
// 传送请求者到接收者位置
|
||||||
|
requester.teleport(receiver.getLocation());
|
||||||
|
plugin.isteleport.put(requester, 1);
|
||||||
|
|
||||||
|
// 5分钟后清理传送状态
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (plugin.isteleport.getOrDefault(requester, 0) != 1) {
|
||||||
|
requester.sendMessage(ChatColor.RED + "返回命令已过期,无法再返回原处");
|
||||||
|
}
|
||||||
|
// 清理相关映射
|
||||||
|
plugin.teleportp.remove(requester);
|
||||||
|
plugin.teleport.remove(requester);
|
||||||
|
plugin.isteleport.remove(requester);
|
||||||
|
plugin.Ateleport.remove(receiver);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, 5 * 60 * 20); // 5分钟(20tick=1秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理拒绝对方传送至自己的请求
|
||||||
|
*/
|
||||||
|
private void handleDeny(Player receiver) {
|
||||||
|
Player requester = plugin.Ateleport.get(receiver);
|
||||||
|
if (requester == null || !requester.isOnline()) {
|
||||||
|
receiver.sendMessage(ChatColor.RED + "传送请求已过期或不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送拒绝消息
|
||||||
|
receiver.sendMessage(ChatColor.GREEN + "已拒绝传送请求");
|
||||||
|
requester.sendMessage(ChatColor.RED + "对方已拒绝你的传送请求");
|
||||||
|
|
||||||
|
// 清理相关映射
|
||||||
|
plugin.teleportp.remove(requester);
|
||||||
|
plugin.teleport.remove(requester);
|
||||||
|
plugin.isteleport.remove(requester);
|
||||||
|
plugin.Ateleport.remove(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理同意自己传送至对方的请求
|
||||||
|
*/
|
||||||
|
private void handleTome(Player requester) {
|
||||||
|
// 获取请求接收者(teleport: 请求者 -> 接收者)
|
||||||
|
Player receiver = plugin.teleport.get(requester);
|
||||||
|
if (receiver == null || !receiver.isOnline()) {
|
||||||
|
requester.sendMessage(ChatColor.RED + "传送请求已过期或不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行传送逻辑
|
||||||
|
requester.sendMessage(ChatColor.GREEN + "已同意传送至对方!");
|
||||||
|
receiver.sendMessage(ChatColor.GREEN + "对方已同意传送至你处");
|
||||||
|
|
||||||
|
// 保存请求者原始位置
|
||||||
|
plugin.teleportp.put(requester, requester.getLocation());
|
||||||
|
// 传送请求者到接收者位置
|
||||||
|
requester.teleport(receiver.getLocation());
|
||||||
|
plugin.isteleport.put(requester, 1);
|
||||||
|
|
||||||
|
// 5分钟后清理传送状态
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (plugin.isteleport.getOrDefault(requester, 0) != 1) {
|
||||||
|
requester.sendMessage(ChatColor.RED + "返回命令已过期,无法再返回原处");
|
||||||
|
}
|
||||||
|
// 清理相关映射
|
||||||
|
plugin.teleportp.remove(requester);
|
||||||
|
plugin.Ateleport.remove(receiver);
|
||||||
|
plugin.isteleport.remove(requester);
|
||||||
|
plugin.teleport.remove(requester);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, 5 * 60 * 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理拒绝自己传送至对方的请求
|
||||||
|
*/
|
||||||
|
private void handleDetome(Player requester) {
|
||||||
|
Player receiver = plugin.teleport.get(requester);
|
||||||
|
if (receiver == null || !receiver.isOnline()) {
|
||||||
|
requester.sendMessage(ChatColor.RED + "传送请求已过期或不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送拒绝消息
|
||||||
|
requester.sendMessage(ChatColor.GREEN + "已拒绝传送至对方");
|
||||||
|
receiver.sendMessage(ChatColor.RED + "对方已拒绝传送至你处");
|
||||||
|
|
||||||
|
// 清理相关映射
|
||||||
|
plugin.teleportp.remove(requester);
|
||||||
|
plugin.Ateleport.remove(receiver);
|
||||||
|
plugin.isteleport.remove(requester);
|
||||||
|
plugin.teleport.remove(requester);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.xgqy.survival.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
public class msgCommandExecutor implements CommandExecutor {
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
|
public msgCommandExecutor(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// 仅允许玩家使用
|
||||||
|
if (!(sender instanceof Player sendPlayer)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "无法对非玩家类使用");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验参数长度(至少需要 目标玩家名 + 消息内容)
|
||||||
|
if (args.length < 2) {
|
||||||
|
sendPlayer.sendMessage(ChatColor.RED + "用法错误!正确格式:/msg <玩家名> <消息内容>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理目标玩家
|
||||||
|
String targetName = args[0];
|
||||||
|
Player targetPlayer = Bukkit.getPlayer(targetName);
|
||||||
|
// 校验目标玩家是否存在且在线(避免空指针)
|
||||||
|
if (targetPlayer == null || !targetPlayer.isOnline()) {
|
||||||
|
sendPlayer.sendMessage(ChatColor.RED + "目标玩家不存在或不在线!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接消息内容(从第2个参数开始,处理带空格的消息)
|
||||||
|
StringBuilder messageBuilder = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
messageBuilder.append(args[i]).append(" ");
|
||||||
|
}
|
||||||
|
String originalMessage = messageBuilder.toString().trim(); // 去除首尾空格
|
||||||
|
|
||||||
|
// 拦截空消息
|
||||||
|
if (originalMessage.isEmpty()) {
|
||||||
|
sendPlayer.sendMessage(ChatColor.RED + "不允许发送空消息!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制消息长度为32个字符(中文算1个)
|
||||||
|
String finalMessage = originalMessage;
|
||||||
|
if (originalMessage.length() > 32) {
|
||||||
|
finalMessage = originalMessage.substring(0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验好友关系
|
||||||
|
if (plugin.friends.getOrDefault(sendPlayer, null) == null) {
|
||||||
|
sendPlayer.sendMessage(ChatColor.RED + "您还没有好友哦!输入 /friend add <玩家名> 添加好友吧");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!plugin.friends.get(sendPlayer).contains(targetPlayer)) {
|
||||||
|
sendPlayer.sendMessage(ChatColor.RED + "该玩家不是您的好友!输入 /friend add <玩家名> 添加好友吧");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送私聊消息(双方都能看到,提升体验)
|
||||||
|
// 接收方消息
|
||||||
|
targetPlayer.sendMessage(
|
||||||
|
ChatColor.LIGHT_PURPLE + "" + ChatColor.BOLD + "私聊 " +
|
||||||
|
ChatColor.WHITE + sendPlayer.getName() + " > " + finalMessage
|
||||||
|
);
|
||||||
|
// 发送方回执
|
||||||
|
sendPlayer.sendMessage(
|
||||||
|
ChatColor.LIGHT_PURPLE + "" + ChatColor.BOLD + "私聊 " +
|
||||||
|
ChatColor.WHITE + "你 > " + targetPlayer.getName() + ":" + finalMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,13 @@ import org.bukkit.scoreboard.Objective;
|
|||||||
import org.bukkit.scoreboard.Scoreboard;
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ChatEvent implements Listener {
|
public class ChatEvent implements Listener {
|
||||||
private static final String ADMIN_OBJECTIVE = "administrator";
|
private static final String ADMIN_OBJECTIVE = "administrator";
|
||||||
@@ -20,98 +25,203 @@ public class ChatEvent implements Listener {
|
|||||||
private static final long SHORT_INTERVAL_MUTE_TIME = 10000;
|
private static final long SHORT_INTERVAL_MUTE_TIME = 10000;
|
||||||
private static final long SAME_MESSAGE_MUTE_TIME = 15000;
|
private static final long SAME_MESSAGE_MUTE_TIME = 15000;
|
||||||
private static final long CLEAR_INTERVAL = 30000;
|
private static final long CLEAR_INTERVAL = 30000;
|
||||||
|
private static final long FORBIDDEN_MUTE_TIME = 30 * 1000; // 违禁词禁言30秒
|
||||||
|
private static final int CREDIT_DEDUCTION = 5; // 扣除信用分数
|
||||||
|
|
||||||
private Map<Player, Long> lastChatTime = new HashMap<>();
|
// 核心违禁词库(可根据需求扩展,建议从配置文件读取)
|
||||||
private Map<Player, String> lastMessage = new HashMap<>();
|
private static final Set<String> FORBIDDEN_WORDS = new HashSet<>(Arrays.asList(
|
||||||
private Map<Player, Long> muteEndTime = new HashMap<>();
|
"草", "操", "艹", "妈", "他妈", "你妈", "傻逼", "煞笔", "傻屌", "屌", "鸡巴", "jb", "j8",
|
||||||
|
"卖淫", "嫖娼", "毒品", "大麻", "冰毒", "k粉", "邪教", "反动", "分裂", "台独", "港独", "藏独",
|
||||||
|
"日", "干", "操你妈", "cnm", "nmd", "tmd", "mlgb", "草泥马", "草拟吗", "淫", "毒", "邪", "反",
|
||||||
|
"裂", "独", "法西斯", "恐怖", "炸弹", "杀人", "抢劫", "强奸", "赌博", "诈骗", "传销", "邪教组织"
|
||||||
|
));
|
||||||
|
|
||||||
private Survival plugin;
|
// 常见谐音/变体映射(覆盖拼音、符号替换等规避手段)
|
||||||
|
private static final Map<String, String> HOMOPHONE_MAP = new HashMap<>() {{
|
||||||
|
put("cao", "草"); put("ca", "草"); put("cao", "操"); put("艹", "草");
|
||||||
|
put("ma", "妈"); put("mama", "妈妈"); put("mam", "妈");
|
||||||
|
put("gan", "干"); put("gai", "干"); put("ga", "干");
|
||||||
|
put("ri", "日"); put("r", "日");
|
||||||
|
put("tmd", "他妈的"); put("nmd", "你妈的"); put("nm", "你妈");
|
||||||
|
put("sb", "傻逼"); put("sha", "傻"); put("bi", "逼"); put("shabi", "傻逼"); put("煞笔", "傻逼");
|
||||||
|
put("mlgb", "妈了个逼"); put("cnm", "草你妈"); put("caonima", "草你妈");
|
||||||
|
put("草泥马", "草你妈"); put("草拟吗", "草你妈"); put("caoni", "草你");
|
||||||
|
put("diao", "屌"); put("dia", "屌"); put("jb", "鸡巴"); put("j8", "鸡巴"); put("jj", "鸡鸡");
|
||||||
|
put("piao", "嫖"); put("piaochang", "嫖娼"); put("maiyin", "卖淫");
|
||||||
|
put("du", "毒"); put("duming", "毒品"); put("dama", "大麻"); put("bingdu", "冰毒");
|
||||||
|
put("xiejiao", "邪教"); put("fandong", "反动"); put("fenlie", "分裂");
|
||||||
|
put("taidu", "台独"); put("gangdu", "港独"); put("zangdu", "藏独");
|
||||||
|
}};
|
||||||
|
|
||||||
|
// 需过滤的分隔符正则(匹配所有非文字字符,包括空格、符号、数字等)
|
||||||
|
private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[^a-zA-Z\u4e00-\u9fa5]");
|
||||||
|
|
||||||
|
private final Map<Player, Long> lastChatTime = new HashMap<>();
|
||||||
|
private final Map<Player, String> lastMessage = new HashMap<>();
|
||||||
|
private final Map<Player, Long> muteEndTime = new HashMap<>();
|
||||||
|
private final Survival plugin;
|
||||||
|
|
||||||
public ChatEvent(Survival plugin) {
|
public ChatEvent(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
// 启动定时任务,每1秒检查一次
|
// 定时清理历史聊天记录(每30秒)
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
for (Map.Entry<Player, Long> entry : lastChatTime.entrySet()) {
|
Iterator<Map.Entry<Player, Long>> iterator = lastChatTime.entrySet().iterator();
|
||||||
Player player = entry.getKey();
|
while (iterator.hasNext()) {
|
||||||
long lastTime = entry.getValue();
|
Map.Entry<Player, Long> entry = iterator.next();
|
||||||
if (currentTime - lastTime > CLEAR_INTERVAL) {
|
if (currentTime - entry.getValue() > CLEAR_INTERVAL) {
|
||||||
lastChatTime.remove(player);
|
Player player = entry.getKey();
|
||||||
|
iterator.remove();
|
||||||
lastMessage.remove(player);
|
lastMessage.remove(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.runTaskTimer(this.plugin, 0L, 20L); // 20L表示每1秒执行一次
|
}.runTaskTimer(this.plugin, 0L, 20L * 30); // 30秒执行一次(20tick=1秒)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
|
String originalMessage = event.getMessage().trim();
|
||||||
|
|
||||||
// 检查玩家是否处于禁言状态
|
// 1. 优先检查禁言状态(所有玩家通用)
|
||||||
if (muteEndTime.containsKey(player) && System.currentTimeMillis() < muteEndTime.get(player)) {
|
if (isMuted(player)) {
|
||||||
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
|
long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000;
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.RED + "禁言 |" + ChatColor.WHITE + " " +
|
ChatColor.RED + "禁言提示 |" + ChatColor.WHITE + " " +
|
||||||
ChatColor.GRAY + "请不要重复发送相同消息 (你需要等待: " +
|
ChatColor.GRAY + "你因违规发言被禁言,剩余时间: " +
|
||||||
ChatColor.DARK_RED + remainingSeconds + "s" +
|
ChatColor.DARK_RED + remainingSeconds + "s"
|
||||||
ChatColor.GRAY + "才能发言)"
|
|
||||||
);
|
);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scoreboard scoreboard = player.getScoreboard();
|
// 2. 严格违禁词检测(管理员也需检查,无豁免)
|
||||||
boolean isAdmin = false;
|
if (containsForbiddenContent(originalMessage)) {
|
||||||
Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE);
|
handleForbiddenViolation(player);
|
||||||
if (adminObj != null && adminObj.getScore(player.getName()).getScore() == 1) {
|
// 广播屏蔽后的消息
|
||||||
isAdmin = true;
|
String maskedMessage = ChatColor.GRAY + player.getPlayerListName() + ": " + ChatColor.RED + "***";
|
||||||
|
player.getServer().broadcastMessage(maskedMessage);
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅非管理员受消息长度限制(管理员忽略此检查)
|
// 3. 管理员判断(后续规则仅普通玩家受限)
|
||||||
String message = event.getMessage();
|
boolean isAdmin = isAdministrator(player);
|
||||||
if (!isAdmin && message.length() > MAX_MESSAGE_LENGTH) {
|
|
||||||
event.setCancelled(true);
|
// 4. 非管理员 - 消息长度限制
|
||||||
|
if (!isAdmin && originalMessage.length() > MAX_MESSAGE_LENGTH) {
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.AQUA + "无法发言 |" + ChatColor.WHITE + ": " +
|
ChatColor.AQUA + "发言限制 |" + ChatColor.WHITE + ": " +
|
||||||
ChatColor.RED + "消息过长(" +
|
ChatColor.RED + "消息过长(非管理员仅可发送" + MAX_MESSAGE_LENGTH + "字符以内)" +
|
||||||
ChatColor.YELLOW + "非管理员仅可发送 30 字符以内的消息(String.message.length=" + message.length() + ")" +
|
ChatColor.GRAY + "(当前长度: " + originalMessage.length() + ")"
|
||||||
ChatColor.RED + ")"
|
|
||||||
);
|
);
|
||||||
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查发言间隔和发言内容
|
// 5. 非管理员 - 发言间隔和重复消息检测
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
|
// 发言速度检测
|
||||||
if (lastChatTime.containsKey(player)) {
|
if (lastChatTime.containsKey(player)) {
|
||||||
long interval = currentTime - lastChatTime.get(player);
|
long interval = currentTime - lastChatTime.get(player);
|
||||||
if (interval < SHORT_INTERVAL) {
|
if (interval < SHORT_INTERVAL) {
|
||||||
muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME);
|
muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME);
|
||||||
player.sendMessage(
|
player.sendMessage(
|
||||||
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
|
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
|
||||||
ChatColor.GRAY + "你的发言太快了,你需要等待一会儿才能继续发言"
|
ChatColor.GRAY + "发言速度过快,请等待1秒后再试"
|
||||||
);
|
|
||||||
event.setCancelled(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(message)) {
|
|
||||||
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
|
|
||||||
player.sendMessage(
|
|
||||||
ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " +
|
|
||||||
ChatColor.RED + "请不要发送相同的消息,你需要等待一会儿才能继续发言"
|
|
||||||
);
|
);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 重复消息检测
|
||||||
|
if (lastMessage.containsKey(player) && lastMessage.get(player).equals(originalMessage)) {
|
||||||
|
muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME);
|
||||||
|
player.sendMessage(
|
||||||
|
ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " +
|
||||||
|
ChatColor.GRAY + "禁止发送重复消息,请等待15秒后再试"
|
||||||
|
);
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 更新历史记录
|
||||||
lastChatTime.put(player, currentTime);
|
lastChatTime.put(player, currentTime);
|
||||||
lastMessage.put(player, message);
|
lastMessage.put(player, originalMessage);
|
||||||
}
|
}
|
||||||
String formattedMessage = player.getPlayerListName() + ":" + message;
|
|
||||||
|
// 6. 正常消息格式化发送
|
||||||
|
String formattedMessage = ChatColor.WHITE + player.getPlayerListName() + ": " + ChatColor.GRAY + originalMessage;
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
player.getServer().broadcastMessage(formattedMessage);
|
player.getServer().broadcastMessage(formattedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 严格违禁词检测核心方法(覆盖多种规避手段)
|
||||||
|
* 处理流程:1. 清理文本 → 2. 谐音替换 → 3. 违禁词匹配
|
||||||
|
*/
|
||||||
|
private boolean containsForbiddenContent(String text) {
|
||||||
|
if (text == null || text.isEmpty()) return false;
|
||||||
|
|
||||||
|
// 步骤1:清理文本(移除所有分隔符、数字、符号,保留中英文)
|
||||||
|
String cleanedText = SEPARATOR_PATTERN.matcher(text).replaceAll("");
|
||||||
|
if (cleanedText.isEmpty()) return false;
|
||||||
|
|
||||||
|
// 步骤2:谐音/变体替换(将拼音、符号变体转为核心违禁词)
|
||||||
|
String convertedText = cleanedText.toLowerCase();
|
||||||
|
for (Map.Entry<String, String> entry : HOMOPHONE_MAP.entrySet()) {
|
||||||
|
convertedText = convertedText.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤3:检测是否包含任何违禁词(子串匹配,覆盖组合词)
|
||||||
|
for (String forbiddenWord : FORBIDDEN_WORDS) {
|
||||||
|
if (convertedText.contains(forbiddenWord)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理违禁词违规:扣除信用分 + 30秒禁言 + 提示
|
||||||
|
*/
|
||||||
|
private void handleForbiddenViolation(Player player) {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
// 1. 设置30秒禁言
|
||||||
|
muteEndTime.put(player, currentTime + FORBIDDEN_MUTE_TIME);
|
||||||
|
|
||||||
|
// 2. 扣除5点信用分(同步执行命令,避免异步线程问题)
|
||||||
|
BukkitRunnable runnable = new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String command = "point " + player.getName() + " remove " + CREDIT_DEDUCTION;
|
||||||
|
// 用控制台执行命令,确保权限足够
|
||||||
|
plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), command);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
runnable.runTask(plugin); // 同步到主线程执行
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查玩家是否处于禁言状态
|
||||||
|
*/
|
||||||
|
private boolean isMuted(Player player) {
|
||||||
|
if (!muteEndTime.containsKey(player)) return false;
|
||||||
|
return System.currentTimeMillis() < muteEndTime.get(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查玩家是否为管理员(通过计分板标记)
|
||||||
|
*/
|
||||||
|
private boolean isAdministrator(Player player) {
|
||||||
|
Scoreboard scoreboard = player.getScoreboard();
|
||||||
|
Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE);
|
||||||
|
if (adminObj == null) return false;
|
||||||
|
// 计分板分数为1表示管理员
|
||||||
|
return adminObj.getScore(player.getName()).getScore() == 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -45,12 +45,14 @@ public class ChooseTagEvent implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.getView().getTitle().contains("称号") && !event.getView().getTitle().contains("商城")) {
|
if (!event.getView().getTitle().contains("称号") && !event.getView().getTitle().contains("商城")&& !event.getView().getTitle().contains("背包")&& !event.getView().getTitle().contains("签到")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
if (event.getView().getTitle().contains("称号")) {
|
if(event.getView().getTitle().contains("签到")){
|
||||||
|
|
||||||
|
} else if (event.getView().getTitle().contains("称号")) {
|
||||||
ItemStack clickedItem = event.getCurrentItem();
|
ItemStack clickedItem = event.getCurrentItem();
|
||||||
if (clickedItem == null || clickedItem.getType() != Material.NAME_TAG) {
|
if (clickedItem == null || clickedItem.getType() != Material.NAME_TAG) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
128
src/main/java/org/xgqy/survival/event/CommandLimiter.java
Normal file
128
src/main/java/org/xgqy/survival/event/CommandLimiter.java
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.BanList;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class CommandLimiter implements Listener {
|
||||||
|
// 核心配置常量
|
||||||
|
private static final int TEN_SEC_LIMIT = 5; // 10秒内最大命令数
|
||||||
|
private static final int ONE_MIN_LIMIT = 15; // 1分钟内最大命令数
|
||||||
|
private static final long TEN_SEC_MS = 10000; // 10秒(毫秒)
|
||||||
|
private static final long ONE_MIN_MS = 60000; // 1分钟(毫秒)
|
||||||
|
private static final long BAN_DURATION = 60; // 封禁时长(秒)
|
||||||
|
private static final String BAN_REASON = "DDoS攻击"; // 封禁理由
|
||||||
|
|
||||||
|
private final Survival plugin;
|
||||||
|
// 存储玩家命令时间戳:key=玩家UUID,value=命令发送时间戳列表
|
||||||
|
private final Map<UUID, List<Long>> playerCommandTimes = new HashMap<>();
|
||||||
|
// 存储已封禁玩家,避免重复封禁
|
||||||
|
private final Map<UUID, Boolean> bannedPlayers = new HashMap<>();
|
||||||
|
|
||||||
|
public CommandLimiter(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// 注册事件监听器
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听玩家命令发送事件
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
UUID playerUuid = player.getUniqueId();
|
||||||
|
|
||||||
|
// 跳过已封禁玩家的命令处理
|
||||||
|
if (bannedPlayers.getOrDefault(playerUuid, false) || isPlayerBanned(player)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
player.sendMessage("§c你因" + BAN_REASON + "已被封禁" + BAN_DURATION + "秒!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化玩家命令时间列表
|
||||||
|
playerCommandTimes.computeIfAbsent(playerUuid, k -> new ArrayList<>());
|
||||||
|
List<Long> commandTimes = playerCommandTimes.get(playerUuid);
|
||||||
|
|
||||||
|
// 添加当前命令时间戳
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
commandTimes.add(currentTime);
|
||||||
|
|
||||||
|
// 清理过期时间戳(只保留1分钟内的,节省内存)
|
||||||
|
commandTimes.removeIf(time -> time < currentTime - ONE_MIN_MS);
|
||||||
|
|
||||||
|
// 统计10秒内和1分钟内的命令数
|
||||||
|
long tenSecCount = commandTimes.stream()
|
||||||
|
.filter(time -> time >= currentTime - TEN_SEC_MS)
|
||||||
|
.count();
|
||||||
|
long oneMinCount = commandTimes.size();
|
||||||
|
|
||||||
|
// 检查是否触发封禁条件
|
||||||
|
if (tenSecCount > TEN_SEC_LIMIT || oneMinCount > ONE_MIN_LIMIT) {
|
||||||
|
banPlayer(player);
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听玩家退出事件,清理数据(避免内存泄漏)
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
UUID playerUuid = event.getPlayer().getUniqueId();
|
||||||
|
playerCommandTimes.remove(playerUuid);
|
||||||
|
bannedPlayers.remove(playerUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封禁玩家指定时长
|
||||||
|
*/
|
||||||
|
private void banPlayer(Player player) {
|
||||||
|
UUID playerUuid = player.getUniqueId();
|
||||||
|
// 标记为已封禁,避免重复处理
|
||||||
|
bannedPlayers.put(playerUuid, true);
|
||||||
|
|
||||||
|
// 执行封禁操作
|
||||||
|
Bukkit.getBanList(BanList.Type.NAME).addBan(
|
||||||
|
player.getName(),
|
||||||
|
BAN_REASON,
|
||||||
|
new java.util.Date(System.currentTimeMillis() + BAN_DURATION * 1000),
|
||||||
|
"系统"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 踢出玩家
|
||||||
|
player.kickPlayer("§c你因" + BAN_REASON + "被封禁" + BAN_DURATION + "秒!");
|
||||||
|
|
||||||
|
// 封禁到期后移除标记
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
bannedPlayers.remove(playerUuid);
|
||||||
|
// 可选:发送解封通知(如果玩家已重新加入)
|
||||||
|
Player onlinePlayer = Bukkit.getPlayer(playerUuid);
|
||||||
|
if (onlinePlayer != null && onlinePlayer.isOnline()) {
|
||||||
|
//onlinePlayer.sendMessage("§a你的" + BAN_DURATION + "秒封禁已到期,可正常使用命令!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, BAN_DURATION * 20); // 20tick=1秒
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查玩家是否处于封禁状态
|
||||||
|
*/
|
||||||
|
private boolean isPlayerBanned(Player player) {
|
||||||
|
return Bukkit.getBanList(BanList.Type.NAME).isBanned(player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/java/org/xgqy/survival/event/FartExecutor.java
Normal file
7
src/main/java/org/xgqy/survival/event/FartExecutor.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
public class FartExecutor implements Listener {
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/org/xgqy/survival/event/ForceSurvival.java
Normal file
18
src/main/java/org/xgqy/survival/event/ForceSurvival.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerGameModeChangeEvent;
|
||||||
|
|
||||||
|
public class ForceSurvival implements Listener {
|
||||||
|
@EventHandler
|
||||||
|
private void gamemodechange(PlayerGameModeChangeEvent event){
|
||||||
|
if(event.getNewGameMode() != GameMode.SURVIVAL){
|
||||||
|
event.getPlayer().setGameMode(GameMode.SURVIVAL);
|
||||||
|
event.setCancelled(true);
|
||||||
|
event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +1,135 @@
|
|||||||
package org.xgqy.survival.event;
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.xgqy.survival.PlayerTags;
|
import org.xgqy.survival.PlayerTags;
|
||||||
import org.xgqy.survival.Survival;
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class JoinEvent implements Listener {
|
public class JoinEvent implements Listener {
|
||||||
private Survival plugin;
|
private Survival plugin;
|
||||||
|
|
||||||
|
// 名称违禁词库(覆盖常见违规词汇,含中文、英文、缩写,统一小写存储)
|
||||||
|
private static final Set<String> NAME_FORBIDDEN_WORDS = new HashSet<>(Arrays.asList(
|
||||||
|
"傻逼", "煞笔", "傻屌", "屌", "操", "草", "艹", "妈蛋", "你妈", "他妈",
|
||||||
|
"cnm", "nmd", "tmd", "mlgb", "sb", "shabi", "cao", "gan", "ri",
|
||||||
|
"鸡巴", "jb", "j8", "嫖娼", "卖淫", "毒品", "大麻", "邪教", "反动",
|
||||||
|
"分裂", "台独", "港独", "藏独", "法西斯", "恐怖", "炸弹", "杀人",
|
||||||
|
"抢劫", "强奸", "赌博", "诈骗", "传销", "淫", "毒", "邪", "反", "裂", "独"
|
||||||
|
));
|
||||||
|
|
||||||
public JoinEvent(Survival plugin) {
|
public JoinEvent(Survival plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
private void join(PlayerJoinEvent e) {
|
private void join(PlayerJoinEvent e) {
|
||||||
PlayerTags playertags = plugin.getPlayerTags();
|
Player player = e.getPlayer();
|
||||||
List<String> tags = playertags.getTags(e.getPlayer());
|
|
||||||
if (!tags.isEmpty() && playertags.getCurrentTag(e.getPlayer()) != -1) {
|
// ########## 新增:名称违禁词检测(优先执行,违规直接踢除)##########
|
||||||
e.getPlayer().setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName());
|
if (containsForbiddenWord(player.getName())) {
|
||||||
e.getPlayer().setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName());
|
String kickMessage = ChatColor.RED + "=====================================\n" +
|
||||||
} else {
|
ChatColor.RED+ "登录失败: 违规用户名\n"+
|
||||||
if (!tags.isEmpty())
|
ChatColor.RED + "=====================================";
|
||||||
e.getPlayer().sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换");
|
player.kickPlayer(kickMessage);
|
||||||
|
e.setJoinMessage(null); // 取消违规名称的加入广播
|
||||||
|
return; // 终止后续所有逻辑执行
|
||||||
}
|
}
|
||||||
e.setJoinMessage(e.getPlayer().getPlayerListName() + " 加入了 生存1区");
|
|
||||||
e.getPlayer().sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!");
|
// 原有逻辑:强制生存模式
|
||||||
e.getPlayer().sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助");
|
if (player.getGameMode() != GameMode.SURVIVAL) {
|
||||||
e.getPlayer().sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20);
|
player.setGameMode(GameMode.SURVIVAL);
|
||||||
|
player.sendMessage(ChatColor.AQUA + "星阁钱语 " + ChatColor.WHITE + "| " + ChatColor.RED + "您的游戏模式已经被强制切换为 生存模式");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:信用分<=0 永久封禁
|
||||||
|
if (plugin.ppoint.getOrDefault(player, 100) <= 0) {
|
||||||
|
player.kickPlayer(ChatColor.RED + "您好! 您由于 信用分小于 0 ,我们决定对你的账号 " + player.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:初始化信用分(默认100)
|
||||||
|
if (plugin.ppoint.getOrDefault(player, 100) == 100) {
|
||||||
|
plugin.ppoint.put(player, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:称号设置
|
||||||
|
PlayerTags playertags = plugin.getPlayerTags();
|
||||||
|
List<String> tags = playertags.getTags(player);
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
if (playertags.getCurrentTag(player) != -1) {
|
||||||
|
player.setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换");
|
||||||
|
// 补充:自动设置第一个称号(原逻辑只提示未设置,此处优化体验)
|
||||||
|
playertags.setSelectedTag(player,0);
|
||||||
|
player.setDisplayName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:首次登录随机传送
|
||||||
|
if (player.getScoreboard().getObjective("logged") != null && player.getScoreboard().getObjective("logged").getScore(player).getScore() == 0) {
|
||||||
|
double x = Math.random() * 10000;
|
||||||
|
double z = Math.random() * 10000;
|
||||||
|
int bx = (int) x, bz = (int) z;
|
||||||
|
|
||||||
|
// 修正:原循环逻辑存在死循环风险,优化条件判断
|
||||||
|
while (true) {
|
||||||
|
// 避开出生点区域(-100~150 X/Z范围)
|
||||||
|
boolean inSpawnArea = (x >= -100 && x <= 150) || (z >= -180 && z <= 150);
|
||||||
|
// 确保最高方块不是水/ lava
|
||||||
|
Material highestBlock = player.getWorld().getHighestBlockAt(bx, bz).getType();
|
||||||
|
boolean isDangerous = highestBlock == Material.WATER || highestBlock == Material.LAVA;
|
||||||
|
|
||||||
|
if (inSpawnArea || isDangerous) {
|
||||||
|
x = Math.random() * 10000;
|
||||||
|
z = Math.random() * 10000;
|
||||||
|
bx = (int) x;
|
||||||
|
bz = (int) z;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int highestY = player.getWorld().getHighestBlockYAt(bx, bz);
|
||||||
|
Location spawnLoc = new Location(player.getWorld(), x, highestY + 1, z); // +1避免卡在方块里
|
||||||
|
player.teleport(spawnLoc);
|
||||||
|
player.getScoreboard().getObjective("logged").getScore(player).setScore(1);
|
||||||
|
player.setRespawnLocation(spawnLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有逻辑:加入消息和欢迎提示
|
||||||
|
e.setJoinMessage(player.getPlayerListName() + " 加入了 生存1区");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!");
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助");
|
||||||
|
player.sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心检测:判断玩家名称是否包含违禁词
|
||||||
|
* 特性:大小写不敏感、忽略空格、子串匹配(覆盖变体规避)
|
||||||
|
*/
|
||||||
|
private boolean containsForbiddenWord(String playerName) {
|
||||||
|
// 预处理:转为小写 + 移除所有空格(应对"傻 逼"这类分隔规避)
|
||||||
|
String processedName = playerName.toLowerCase().replaceAll("\\s+", "");
|
||||||
|
// 遍历违禁词库,检测是否包含子串
|
||||||
|
for (String forbiddenWord : NAME_FORBIDDEN_WORDS) {
|
||||||
|
if (processedName.contains(forbiddenWord)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
266
src/main/java/org/xgqy/survival/event/LandEvent.java
Normal file
266
src/main/java/org/xgqy/survival/event/LandEvent.java
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
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.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class LandEvent implements Listener {
|
||||||
|
private final Survival plugin;
|
||||||
|
// 缓存玩家当前所在的领地所有者(避免重复发送消息)
|
||||||
|
private final WeakHashMap<Player, String> currentLandOwner = new WeakHashMap<>();
|
||||||
|
|
||||||
|
public LandEvent(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:计算两个坐标的最小值和最大值
|
||||||
|
* @return 长度为2的数组,[0]是最小值,[1]是最大值
|
||||||
|
*/
|
||||||
|
private int[] getMinMax(int a, int b) {
|
||||||
|
return new int[]{Math.min(a, b), Math.max(a, b)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查位置是否在领地的x-z范围内
|
||||||
|
*/
|
||||||
|
private boolean isInLand(Location loc, Survival.Land land) {
|
||||||
|
if (loc == null || land == null) return false;
|
||||||
|
Location start = land.getStart();
|
||||||
|
Location end = land.getEnd();
|
||||||
|
if (start == null || end == null) return false; // 领地坐标无效
|
||||||
|
// 检查是否在同一个世界
|
||||||
|
if (!loc.getWorld().equals(start.getWorld())) return false;
|
||||||
|
|
||||||
|
int[] xRange = getMinMax(start.getBlockX(), end.getBlockX());
|
||||||
|
int[] zRange = getMinMax(start.getBlockZ(), end.getBlockZ());
|
||||||
|
|
||||||
|
int locX = loc.getBlockX();
|
||||||
|
int locZ = loc.getBlockZ();
|
||||||
|
|
||||||
|
return locX >= xRange[0] && locX <= xRange[1] && locZ >= zRange[0] && locZ <= zRange[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
// 仅当玩家跨方块移动时才处理(减少触发频率)
|
||||||
|
Location from = event.getFrom();
|
||||||
|
Location to = event.getTo();
|
||||||
|
if (to == null || (from.getBlockX() == to.getBlockX() && from.getBlockZ() == to.getBlockZ())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Survival.Land> lands = plugin.land;
|
||||||
|
if (lands == null || lands.isEmpty()) return; // 无领地数据时直接返回
|
||||||
|
|
||||||
|
String previousOwner = currentLandOwner.getOrDefault(player, null);
|
||||||
|
String newOwner = null;
|
||||||
|
Survival.Land enteredLand = null;
|
||||||
|
|
||||||
|
// 查找玩家当前所在的领地
|
||||||
|
for (Survival.Land land : lands) {
|
||||||
|
if (isInLand(to, land)) {
|
||||||
|
newOwner = land.getOwner();
|
||||||
|
enteredLand = land;
|
||||||
|
break; // 找到第一个包含该位置的领地(若有重叠需额外处理)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况1:进入新领地(与之前不同)
|
||||||
|
if (newOwner != null && !Objects.equals(newOwner, previousOwner) && enteredLand != null) {
|
||||||
|
currentLandOwner.put(player, newOwner);
|
||||||
|
plugin.inland.put(player, newOwner); // 更新缓存
|
||||||
|
|
||||||
|
// 不是自己的领地才显示权限
|
||||||
|
if (!Objects.equals(newOwner, player.getName())) {
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已进入玩家 " + ChatColor.WHITE + newOwner + ChatColor.GOLD + " 的领地");
|
||||||
|
|
||||||
|
// 获取当前领地的权限(使用找到的enteredLand,而非lands列表)
|
||||||
|
List<Boolean> perm = enteredLand.getPerm(player);
|
||||||
|
List<Boolean> defaultPerm = enteredLand.getDefaultperm();
|
||||||
|
|
||||||
|
// 权限为空时使用默认权限,并设置到领地中
|
||||||
|
if (perm == null) {
|
||||||
|
// 确保默认权限不为null,避免后续get(i)报错
|
||||||
|
defaultPerm = (defaultPerm != null) ? defaultPerm : List.of(false, false, false, false, false, false, false);
|
||||||
|
perm = defaultPerm;
|
||||||
|
enteredLand.setPerm(player, perm); // 存储权限(Land类setPerm会用玩家名作为键)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送权限信息(确保权限列表长度足够,避免索引越界)
|
||||||
|
player.sendMessage(ChatColor.GREEN + "你在本领地里的权限为:");
|
||||||
|
sendPermissionMessage(player, perm, 0, "破坏");
|
||||||
|
sendPermissionMessage(player, perm, 1, "放置");
|
||||||
|
sendPermissionMessage(player, perm, 2, "飞行");
|
||||||
|
sendPermissionMessage(player, perm, 3, "交互");
|
||||||
|
sendPermissionMessage(player, perm, 4, "使用方块");
|
||||||
|
sendPermissionMessage(player, perm, 5, "TNT爆炸");
|
||||||
|
sendPermissionMessage(player, perm, 6, "玩家间伤害");
|
||||||
|
|
||||||
|
// 处理飞行权限
|
||||||
|
if (isValidPermIndex(perm, 2) && perm.get(2)) {
|
||||||
|
player.setFlySpeed(0.1f); // 恢复默认飞行速度
|
||||||
|
player.setFlying(true);
|
||||||
|
} else {
|
||||||
|
player.setFlying(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 进入自己的领地
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已进入自己的领地");
|
||||||
|
player.setFlying(false); // 自己领地不强制飞行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况2:离开所有领地(之前在领地,现在不在)
|
||||||
|
if (newOwner == null && previousOwner != null) {
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已离开玩家 " + previousOwner + ChatColor.GOLD + " 的领地");
|
||||||
|
currentLandOwner.remove(player);
|
||||||
|
plugin.inland.put(player, null);
|
||||||
|
player.setFlying(false); // 离开领地关闭飞行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:发送权限信息(避免索引越界)
|
||||||
|
*/
|
||||||
|
private void sendPermissionMessage(Player player, List<Boolean> perm, int index, String action) {
|
||||||
|
if (isValidPermIndex(perm, index)) {
|
||||||
|
player.sendMessage(ChatColor.YELLOW + action + ":" + (perm.get(index) ? "允许" : "不允许"));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatColor.YELLOW + action + ":" + "未设置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具方法:检查权限索引是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidPermIndex(List<Boolean> perm, int index) {
|
||||||
|
return perm != null && index >= 0 && index < perm.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
checkActionPermission(player, event.getBlock().getLocation(), 0, () -> {
|
||||||
|
event.setCancelled(true);
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "破坏" + ChatColor.GOLD + " 权限");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
checkActionPermission(player, event.getBlock().getLocation(), 1, () -> {
|
||||||
|
event.setCancelled(true);
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "放置" + ChatColor.GOLD + " 权限");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onPlayerInteract(PlayerInteractEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
// 仅处理方块点击动作
|
||||||
|
if (event.getAction() != Action.LEFT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.getClickedBlock() == null) return; // 点击的方块为空时不处理
|
||||||
|
|
||||||
|
// 交互权限(索引3)和使用方块权限(索引4)分别判断
|
||||||
|
Location loc = event.getClickedBlock().getLocation();
|
||||||
|
checkActionPermission(player, loc, 3, () -> {
|
||||||
|
event.setCancelled(true);
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "交互" + ChatColor.GOLD + " 权限");
|
||||||
|
});
|
||||||
|
checkActionPermission(player, loc, 4, () -> {
|
||||||
|
event.setCancelled(true);
|
||||||
|
player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "使用方块" + ChatColor.GOLD + " 权限");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onEntityExplode(EntityExplodeEvent event) {
|
||||||
|
Location explodeLoc = event.getLocation();
|
||||||
|
List<Survival.Land> lands = plugin.land;
|
||||||
|
if (lands == null || lands.isEmpty()) return;
|
||||||
|
|
||||||
|
// 检查爆炸位置是否在某个领地内,且TNT爆炸权限关闭
|
||||||
|
for (Survival.Land land : lands) {
|
||||||
|
if (isInLand(explodeLoc, land)) {
|
||||||
|
// 获取爆炸的触发玩家(如TNT的放置者,这里简化处理)
|
||||||
|
Player causer = null;
|
||||||
|
Entity entity = event.getEntity();
|
||||||
|
if (entity != null && entity.getCustomName() != null) {
|
||||||
|
// 假设TNT的自定义名称存储了放置者(实际需根据插件逻辑获取)
|
||||||
|
String playerName = entity.getCustomName();
|
||||||
|
causer = plugin.getServer().getPlayer(playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取权限(无触发者则用默认权限)
|
||||||
|
List<Boolean> perm = (causer != null) ? land.getPerm(causer) : land.getDefaultperm();
|
||||||
|
if (perm == null) {
|
||||||
|
perm = List.of(false, false, false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TNT爆炸权限(索引5)为false时取消爆炸
|
||||||
|
if (isValidPermIndex(perm, 5) && !perm.get(5)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (causer != null) {
|
||||||
|
causer.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "此领地禁止TNT爆炸");
|
||||||
|
}
|
||||||
|
return; // 一个领地禁止即可取消
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用方法:检查玩家在指定位置的动作权限
|
||||||
|
*/
|
||||||
|
private void checkActionPermission(Player player, Location loc, int permIndex, Runnable onDenied) {
|
||||||
|
if (player == null || loc == null || onDenied == null) return;
|
||||||
|
|
||||||
|
List<Survival.Land> lands = plugin.land;
|
||||||
|
if (lands == null || lands.isEmpty()) return;
|
||||||
|
|
||||||
|
// 查找位置所在的领地
|
||||||
|
for (Survival.Land land : lands) {
|
||||||
|
if (isInLand(loc, land)) {
|
||||||
|
String owner = land.getOwner();
|
||||||
|
if (owner == null) continue; // 领地所有者为空时不处理
|
||||||
|
|
||||||
|
// 自己的领地不限制
|
||||||
|
if (Objects.equals(owner, player.getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
List<Boolean> perm = land.getPerm(player);
|
||||||
|
List<Boolean> defaultPerm = land.getDefaultperm();
|
||||||
|
if (perm == null) {
|
||||||
|
perm = (defaultPerm != null) ? defaultPerm : List.of(false, false, false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限不足时执行操作
|
||||||
|
if (isValidPermIndex(perm, permIndex) && !perm.get(permIndex)) {
|
||||||
|
onDenied.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
385
src/main/java/org/xgqy/survival/event/LoginEvent.java
Normal file
385
src/main/java/org/xgqy/survival/event/LoginEvent.java
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||||
|
import net.md_5.bungee.api.chat.HoverEvent;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
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.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||||
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.event.player.PlayerPickupItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class LoginEvent implements Listener {
|
||||||
|
private final Survival plugin;
|
||||||
|
// 存储未登录玩家的初始位置(登录后恢复用)
|
||||||
|
private final Map<Player, Location> initialLocations = new HashMap<>();
|
||||||
|
// 记录未登录玩家是否已落地(用于死亡保护逻辑)
|
||||||
|
private final Map<Player, Boolean> unloggedPlayerLanded = new HashMap<>();
|
||||||
|
// 落地判定参数(可根据服务器世界设置调整)
|
||||||
|
private static final double LAND_Y_THRESHOLD = 64; // 落地Y轴阈值(默认世界表面平均高度)
|
||||||
|
private static final int HIGH_ALTITUDE_SAFE = 200; // 高危高空判定(超过此高度强制使用重生点)
|
||||||
|
|
||||||
|
public LoginEvent(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
Location initialLoc = player.getLocation().clone();
|
||||||
|
initialLocations.put(player, initialLoc); // 记录初始位置
|
||||||
|
unloggedPlayerLanded.put(player, false); // 初始化未落地状态
|
||||||
|
// 初始化未登录隔离状态:隐身+高处传送+致盲
|
||||||
|
setupUnloggedState(player);
|
||||||
|
|
||||||
|
// 新玩家自动添加"小萌新"标签(30天)
|
||||||
|
if(!plugin.getAccountManager().isRegistered(player.getUniqueId())){
|
||||||
|
if(!plugin.getPlayerTags().getTags(player).contains("小萌新")){
|
||||||
|
plugin.getPlayerTags().addTag(player,"小萌新",30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录提示定时器(每秒发送一次,登录后停止)
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
// 登录成功,恢复正常状态
|
||||||
|
restoreLoggedState(player);
|
||||||
|
initialLocations.remove(player);
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 发送登录/注册提示(修复注册命令格式错误)
|
||||||
|
if (plugin.getAccountManager().isRegistered(player.getUniqueId())) {
|
||||||
|
player.sendMessage("§e欢迎回来! 请使用 §a/login <密码> §e登录");
|
||||||
|
player.sendTitle("§b星阁钱语", "§e请输入 /login <密码> 登录", 0, 100, 0);
|
||||||
|
} else {
|
||||||
|
BaseComponent[] termsMsg = new ComponentBuilder()
|
||||||
|
.append(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)")
|
||||||
|
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html"))
|
||||||
|
.event(new HoverEvent(
|
||||||
|
HoverEvent.Action.SHOW_TEXT,
|
||||||
|
new ComponentBuilder(ChatColor.GRAY + "https://starpavilion.xyz/terms.html").create()
|
||||||
|
))
|
||||||
|
.create();
|
||||||
|
player.spigot().sendMessage(termsMsg);
|
||||||
|
player.sendMessage("§e欢迎来到服务器! 请使用 §a/reg <密码> <确认密码> <确认码> §e注册");
|
||||||
|
player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <确认密码> <确认码>", 0, 100, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 60, 60); // 1秒发送一次
|
||||||
|
|
||||||
|
// 登录超时踢人(60秒)
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
initialLocations.remove(player);
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
|
player.kickPlayer("§c登录超时(60秒未操作)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, 20 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
// 玩家退出时登出账号并清理缓存
|
||||||
|
plugin.getAccountManager().logout(player);
|
||||||
|
initialLocations.remove(player);
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ 核心拦截:阻止所有外界交互 ------------------------------
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
Location from = event.getFrom();
|
||||||
|
Location to = event.getTo();
|
||||||
|
if (to == null) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 允许垂直下落(保留Y轴移动),阻止水平移动(X/Z轴固定)和视角旋转(Yaw/Pitch固定)
|
||||||
|
Location newLoc = new Location(
|
||||||
|
player.getWorld(),
|
||||||
|
from.getX(), // 固定X轴
|
||||||
|
to.getY(), // 允许Y轴下落
|
||||||
|
from.getZ(), // 固定Z轴
|
||||||
|
from.getYaw(), // 固定视角水平旋转
|
||||||
|
from.getPitch() // 固定视角垂直旋转
|
||||||
|
);
|
||||||
|
event.setTo(newLoc);
|
||||||
|
|
||||||
|
// 落地检测:Y轴低于阈值 + 脚下有实体方块
|
||||||
|
if (newLoc.getY() <= LAND_Y_THRESHOLD && isBlockBelow(player)) {
|
||||||
|
unloggedPlayerLanded.put(player, true);
|
||||||
|
player.sendMessage("§a已落地!请尽快登录或注册");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
event.getPlayer().sendMessage("§c请先登录或注册后再聊天");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
|
||||||
|
String command = event.getMessage().toLowerCase().trim();
|
||||||
|
// 仅允许登录/注册命令(支持带参数的完整命令)
|
||||||
|
if (command.startsWith("/reg ") || command.startsWith("/login ")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
event.getPlayer().sendMessage("§c请先登录或注册(仅允许 /reg /login 命令)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止方块破坏
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止方块放置
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止物品/方块交互
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止实体交互(如骑乘、点击实体)
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止拾取物品
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerPickupItem(PlayerPickupItemEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止丢弃物品
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止攻击行为
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
|
||||||
|
if (event.getDamager() instanceof Player player && !plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止打开容器(箱子、熔炉等)
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryOpen(InventoryOpenEvent event) {
|
||||||
|
if (event.getPlayer() instanceof Player player && !plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ 死亡保护:未落地前阻止伤害和死亡 ------------------------------
|
||||||
|
/**
|
||||||
|
* 拦截未登录玩家的伤害(主要是摔落伤害)
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamage(EntityDamageEvent event) {
|
||||||
|
if (event.getEntity() instanceof Player player) {
|
||||||
|
// 未登录且未落地时,拦截所有伤害并恢复生命值
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player) && !unloggedPlayerLanded.getOrDefault(player, false)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
// 强制恢复满生命值和饥饿值
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
player.setSaturation(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截未登录玩家的死亡事件
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||||
|
Player player = event.getEntity();
|
||||||
|
if (!plugin.getAccountManager().isLoggedIn(player)) {
|
||||||
|
event.setDeathMessage("被资本做局了");
|
||||||
|
// 恢复状态,避免死亡惩罚
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
player.setSaturation(20);
|
||||||
|
player.getActivePotionEffects().forEach(effect ->
|
||||||
|
player.removePotionEffect(effect.getType())
|
||||||
|
);
|
||||||
|
player.sendMessage("§c未登录状态下无法死亡,请尽快登录或注册");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ 工具方法 ------------------------------
|
||||||
|
/**
|
||||||
|
* 检测玩家脚下是否有实体方块(落地判定辅助)
|
||||||
|
*/
|
||||||
|
private boolean isBlockBelow(Player player) {
|
||||||
|
Location below = player.getLocation().subtract(0, 1, 0);
|
||||||
|
return below.getBlock().getType().isSolid() && !below.getBlock().getType().isAir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置未登录玩家的隔离状态:隐身+高处传送+致盲+交互阻断
|
||||||
|
*/
|
||||||
|
private void setupUnloggedState(Player player) {
|
||||||
|
// 1. 隐身(对其他玩家不可见)
|
||||||
|
player.setInvisible(true);
|
||||||
|
// 2. 隐藏Tab列表名称
|
||||||
|
player.setPlayerListName("");
|
||||||
|
// 3. 禁用飞行(防止异常移动)
|
||||||
|
player.setAllowFlight(false);
|
||||||
|
player.setFlying(false);
|
||||||
|
// 4. 传送至当前世界高空(Y=255,确保垂直下落路径安全)
|
||||||
|
Location highLoc = new Location(
|
||||||
|
player.getWorld(),
|
||||||
|
player.getLocation().getX(),
|
||||||
|
255,
|
||||||
|
player.getLocation().getZ()
|
||||||
|
);
|
||||||
|
player.teleport(highLoc);
|
||||||
|
// 5. 施加致盲效果(增强隔离体验)
|
||||||
|
player.addPotionEffect(PotionEffectType.BLINDNESS.createEffect(Integer.MAX_VALUE, 0));
|
||||||
|
// 6. 移除所有干扰效果(保留致盲)
|
||||||
|
player.getActivePotionEffects().forEach(effect -> {
|
||||||
|
if (effect.getType() != PotionEffectType.BLINDNESS) {
|
||||||
|
player.removePotionEffect(effect.getType());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 7. 恢复满状态
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复登录玩家的正常状态
|
||||||
|
*/
|
||||||
|
public void restoreLoggedState(Player player) {
|
||||||
|
// 1. 清理未登录状态缓存
|
||||||
|
unloggedPlayerLanded.remove(player);
|
||||||
|
// 2. 恢复可见性
|
||||||
|
player.setInvisible(false);
|
||||||
|
// 3. 恢复Tab列表名称(带标签)
|
||||||
|
List<String> tags = plugin.getPlayerTags().getTags(player);
|
||||||
|
int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player);
|
||||||
|
// 安全处理标签索引(防止数组越界)
|
||||||
|
int validIndex = Math.max(0, Math.min(currentTagIndex, tags.size() - 1));
|
||||||
|
String tag = tags.isEmpty() ? "默认" : tags.get(validIndex);
|
||||||
|
player.setPlayerListName(ChatColor.WHITE + "[" + tag + ChatColor.WHITE + "]" + player.getName());
|
||||||
|
// 4. 恢复飞行权限(默认禁用,根据服务器配置调整)
|
||||||
|
player.setAllowFlight(false);
|
||||||
|
player.setFlying(false);
|
||||||
|
// 5. 移除致盲效果
|
||||||
|
player.removePotionEffect(PotionEffectType.BLINDNESS);
|
||||||
|
// 6. 传送回安全初始位置(避免高空危险)
|
||||||
|
Location initialLoc = initialLocations.getOrDefault(player, player.getWorld().getSpawnLocation());
|
||||||
|
// 高危位置检测:超过200高度强制使用重生点
|
||||||
|
if (initialLoc.getY() > HIGH_ALTITUDE_SAFE) {
|
||||||
|
initialLoc = player.getWorld().getSpawnLocation();
|
||||||
|
}
|
||||||
|
player.teleport(initialLoc);
|
||||||
|
// 7. 恢复生存模式
|
||||||
|
player.setGameMode(GameMode.SURVIVAL);
|
||||||
|
// 8. 发送登录成功提示
|
||||||
|
player.sendMessage("§a登录成功!欢迎回到星阁钱语");
|
||||||
|
player.sendTitle("§b登录成功", "§a祝您游戏愉快", 10, 70, 20);
|
||||||
|
new BukkitRunnable(){
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
player.getScoreboard().getObjective("playtime").getScore(
|
||||||
|
player.getName()).setScore(player.getScoreboard().getObjective(
|
||||||
|
"playtime").getScore(player.getName()).getScore()+1);
|
||||||
|
// 游玩时间
|
||||||
|
// 贡献值
|
||||||
|
/*
|
||||||
|
贡献值计算方法:
|
||||||
|
1. 游玩满 1 min 增加 1
|
||||||
|
2. 游玩满 5 min 增加 1
|
||||||
|
3. 游玩满 10 min 增加 1
|
||||||
|
4. 游玩满 30 min 增加 2
|
||||||
|
5. 游玩满 1h 增加 2
|
||||||
|
6. 游玩大于 1h 增加 1 / 15 min
|
||||||
|
以上单位为 : 1d
|
||||||
|
7. 一个月以内登录超过 7 天增加 5
|
||||||
|
8. 一个月以内登录超过 14 天增加 10
|
||||||
|
9. 一个月以内登录超过 21 天增加 20
|
||||||
|
10. 一个月以内满勤 + 60
|
||||||
|
以上单位为 : 1mo
|
||||||
|
11. 一星期登录超过3天 + 2
|
||||||
|
12. 一星期登录超过5天 + 5
|
||||||
|
13. 一星期满勤 + 11
|
||||||
|
14. 其他活动增加
|
||||||
|
15. 充值{
|
||||||
|
无 *1.0
|
||||||
|
vip *1.1
|
||||||
|
vip+ *1.2
|
||||||
|
MVP *1.4
|
||||||
|
MVP+ *1.7
|
||||||
|
MVP++ *2.0
|
||||||
|
}
|
||||||
|
17. 充值点券 100点券(原始值,向下取整,不满100不算) = 1 贡献值(不叠加)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin,0,20L);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/main/java/org/xgqy/survival/event/QuitEvent.java
Normal file
71
src/main/java/org/xgqy/survival/event/QuitEvent.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.scoreboard.Objective;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class QuitEvent implements Listener {
|
||||||
|
public Survival plugin;
|
||||||
|
public QuitEvent(Survival plugin){
|
||||||
|
this.plugin=plugin;
|
||||||
|
}
|
||||||
|
@EventHandler
|
||||||
|
private void PlayerQuit(PlayerQuitEvent event){
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
plugin.inland.put(player,null);
|
||||||
|
if(event.getPlayer().getScoreboard().getObjective("handled").getScore(event.getPlayer()).getScore() != 0){
|
||||||
|
Objective handledObjective = player.getScoreboard().getObjective("handled");
|
||||||
|
if (handledObjective != null) {
|
||||||
|
int handledScore = handledObjective.getScore(player.getName()).getScore();
|
||||||
|
|
||||||
|
if (handledScore == 1) {
|
||||||
|
// 2. 安全遍历举报者列表
|
||||||
|
Set<Player> reporters = plugin.reportlist.get(player);
|
||||||
|
if (reporters != null) {
|
||||||
|
for (Player repo : reporters) {
|
||||||
|
if (repo.isOnline()) {
|
||||||
|
repo.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " +
|
||||||
|
ChatColor.GREEN + "\n 您举报的玩家 " + ChatColor.RED + ChatColor.BOLD +
|
||||||
|
player.getName() + ChatColor.GREEN + " 已经被封禁\n"+ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | \n"+ChatColor.GREEN +" 感谢您为维护游戏平衡做贡献!");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 清理数据
|
||||||
|
plugin.reportlist.remove(player); // 直接移除条目
|
||||||
|
plugin.banreason.remove(player);
|
||||||
|
|
||||||
|
// 4. 安全删除banlist中的条目
|
||||||
|
Iterator<Map.Entry<Player, Player>> iterator = plugin.banlist.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<Player, Player> entry = iterator.next();
|
||||||
|
if (player.equals(entry.getValue())) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setQuitMessage(ChatColor.AQUA + "StarPavilion"
|
||||||
|
+ ChatColor.DARK_AQUA + "NetWork"
|
||||||
|
+ ChatColor.WHITE + " | \n"
|
||||||
|
+ ChatColor.YELLOW + " 一名违规玩家 "
|
||||||
|
+ ChatColor.RED + ChatColor.BOLD + player.getName()+ChatColor.YELLOW
|
||||||
|
+ " 被净化了\n");
|
||||||
|
} else {
|
||||||
|
event.setQuitMessage(ChatColor.RED + "玩家 " + player.getName() + " 离开了游戏!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 当handled目标不存在时的默认处理
|
||||||
|
event.setQuitMessage(ChatColor.RED + "玩家 " + player.getName() + " 离开了游戏!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
267
src/main/java/org/xgqy/survival/event/RankAdd.java
Normal file
267
src/main/java/org/xgqy/survival/event/RankAdd.java
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package org.xgqy.survival.event;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.xgqy.survival.Survival;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贡献值管理类:负责贡献值计算、存储、查询、修改
|
||||||
|
*/
|
||||||
|
public class RankAdd {
|
||||||
|
|
||||||
|
// 插件实例
|
||||||
|
private final Survival plugin;
|
||||||
|
// Gson 序列化工具(格式化输出)
|
||||||
|
private final Gson gson;
|
||||||
|
// 数据文件(存储所有玩家贡献值)
|
||||||
|
private final File contributionFile;
|
||||||
|
|
||||||
|
// 内存缓存:玩家UUID -> 贡献值数据
|
||||||
|
private Map<String, PlayerContribution> contributionMap;
|
||||||
|
|
||||||
|
// 贡献类型枚举(可扩展,每种类型对应默认贡献值)
|
||||||
|
public enum ContributionType {
|
||||||
|
TASK_MAIN(10), // 主线任务
|
||||||
|
TASK_DAILY(5), // 日常任务
|
||||||
|
DONATE_ITEM(3), // 捐赠物品
|
||||||
|
EVENT_PARTICIPATE(4),// 参与活动
|
||||||
|
BUILD_PUBLIC(8), // 建造公共设施
|
||||||
|
HELP_PLAYER(2); // 帮助其他玩家
|
||||||
|
|
||||||
|
private final int defaultValue; // 该类型默认贡献值
|
||||||
|
|
||||||
|
ContributionType(int defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 玩家贡献值实体类(存储单个玩家的贡献数据)
|
||||||
|
*/
|
||||||
|
public static class PlayerContribution {
|
||||||
|
private String playerUuid; // 玩家唯一标识(防改名)
|
||||||
|
private String playerName; // 玩家当前名称
|
||||||
|
private int totalContribution; // 总贡献值
|
||||||
|
private long lastUpdateTime; // 最后更新时间(毫秒)
|
||||||
|
private List<ContributionRecord> records; // 贡献记录(追溯用)
|
||||||
|
|
||||||
|
// Gson 序列化必需的默认构造函数
|
||||||
|
public PlayerContribution() {}
|
||||||
|
|
||||||
|
public PlayerContribution(String playerUuid, String playerName) {
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.playerName = playerName;
|
||||||
|
this.totalContribution = 0;
|
||||||
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
this.records = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter 和 Setter
|
||||||
|
public String getPlayerUuid() { return playerUuid; }
|
||||||
|
public void setPlayerUuid(String playerUuid) { this.playerUuid = playerUuid; }
|
||||||
|
public String getPlayerName() { return playerName; }
|
||||||
|
public void setPlayerName(String playerName) { this.playerName = playerName; }
|
||||||
|
public int getTotalContribution() { return totalContribution; }
|
||||||
|
public void setTotalContribution(int totalContribution) { this.totalContribution = totalContribution; }
|
||||||
|
public long getLastUpdateTime() { return lastUpdateTime; }
|
||||||
|
public void setLastUpdateTime(long lastUpdateTime) { this.lastUpdateTime = lastUpdateTime; }
|
||||||
|
public List<ContributionRecord> getRecords() { return records; }
|
||||||
|
public void setRecords(List<ContributionRecord> records) { this.records = records; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贡献记录实体类(记录每次贡献值变动)
|
||||||
|
*/
|
||||||
|
public static class ContributionRecord {
|
||||||
|
private ContributionType type; // 贡献类型
|
||||||
|
private int value; // 变动值(正数=增加,负数=减少)
|
||||||
|
private long timestamp; // 变动时间(毫秒)
|
||||||
|
private String reason; // 变动原因(可选,如"完成主线任务:击败末影龙")
|
||||||
|
|
||||||
|
public ContributionRecord(ContributionType type, int value, String reason) {
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter
|
||||||
|
public ContributionType getType() { return type; }
|
||||||
|
public int getValue() { return value; }
|
||||||
|
public long getTimestamp() { return timestamp; }
|
||||||
|
public String getReason() { return reason; }
|
||||||
|
}
|
||||||
|
public List<PlayerContribution> getAllContributions() {
|
||||||
|
List<PlayerContribution> allData = new ArrayList<>(contributionMap.values());
|
||||||
|
allData.sort((d1, d2) -> Integer.compare(d2.getTotalContribution(), d1.getTotalContribution()));
|
||||||
|
return allData;
|
||||||
|
}
|
||||||
|
public RankAdd(Survival plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
this.contributionFile = new File(plugin.getDataFolder(), "contributions.json");
|
||||||
|
this.contributionMap = loadData();
|
||||||
|
}
|
||||||
|
public int addContribution(@NotNull Player player, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
return modifyContribution(player, type.getDefaultValue(), type, reason);
|
||||||
|
}
|
||||||
|
public int addCustomContribution(@Nullable Player player, int customValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
if (customValue <= 0) {
|
||||||
|
plugin.getLogger().warning("添加贡献值失败:自定义值必须为正数");
|
||||||
|
return player != null ? getContribution(player) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理离线玩家:通过OfflinePlayer的UUID获取数据
|
||||||
|
String uuidStr;
|
||||||
|
String playerName;
|
||||||
|
if (player != null) {
|
||||||
|
uuidStr = player.getUniqueId().toString();
|
||||||
|
playerName = player.getName();
|
||||||
|
} else {
|
||||||
|
// 若player为null(离线),需从贡献值缓存中查找(仅支持已有记录的离线玩家)
|
||||||
|
// 注:新增离线玩家需先通过UUID创建数据,此处仅处理已有记录的情况
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerContribution data = contributionMap.computeIfAbsent(uuidStr, k -> new PlayerContribution(uuidStr, playerName));
|
||||||
|
int newValue = Math.max(0, data.getTotalContribution() + customValue);
|
||||||
|
data.setTotalContribution(newValue);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
data.setPlayerName(playerName);
|
||||||
|
data.getRecords().add(new ContributionRecord(type, customValue, reason));
|
||||||
|
|
||||||
|
saveData();
|
||||||
|
plugin.getLogger().info("添加贡献值:" + playerName + " +" + customValue + ",原因:" + reason + ",当前:" + newValue);
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
public int reduceContribution(@NotNull Player player, int reduceValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
if (reduceValue <= 0) {
|
||||||
|
plugin.getLogger().warning("扣除玩家 " + player.getName() + " 贡献值失败:扣除值必须为正数");
|
||||||
|
return getContribution(player);
|
||||||
|
}
|
||||||
|
return modifyContribution(player, -reduceValue, type, reason);
|
||||||
|
}
|
||||||
|
public int getContribution(@NotNull Player player) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
return data.getTotalContribution();
|
||||||
|
}
|
||||||
|
public List<ContributionRecord> getContributionRecords(@NotNull Player player) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
List<ContributionRecord> records = data.getRecords();
|
||||||
|
// 倒序排列(最新记录在前)
|
||||||
|
records.sort((r1, r2) -> Long.compare(r2.getTimestamp(), r1.getTimestamp()));
|
||||||
|
// 只返回最近10条,避免数据过多
|
||||||
|
int end = Math.min(10, records.size());
|
||||||
|
return records.subList(0, end);
|
||||||
|
}
|
||||||
|
public void resetContribution(@NotNull Player player, @NotNull String reason) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
int oldValue = data.getTotalContribution();
|
||||||
|
data.setTotalContribution(0);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
// 添加重置记录(变动值为 -旧值)
|
||||||
|
data.getRecords().add(new ContributionRecord(ContributionType.TASK_MAIN, -oldValue, "重置贡献值:" + reason));
|
||||||
|
saveData(); // 保存到文件
|
||||||
|
plugin.getLogger().info("玩家 " + player.getName() + " 的贡献值已重置,原数值:" + oldValue + ",原因:" + reason);
|
||||||
|
}
|
||||||
|
public List<PlayerContribution> getTopContributors() {
|
||||||
|
List<PlayerContribution> allData = new ArrayList<>(contributionMap.values());
|
||||||
|
// 按总贡献值降序排序
|
||||||
|
allData.sort((d1, d2) -> Integer.compare(d2.getTotalContribution(), d1.getTotalContribution()));
|
||||||
|
// 返回前10名
|
||||||
|
int end = Math.min(10, allData.size());
|
||||||
|
return allData.subList(0, end);
|
||||||
|
}
|
||||||
|
private int modifyContribution(@NotNull Player player, int changeValue, @NotNull ContributionType type, @NotNull String reason) {
|
||||||
|
PlayerContribution data = getOrCreatePlayerData(player);
|
||||||
|
|
||||||
|
// 计算新贡献值(最低为0,不允许负贡献)
|
||||||
|
int newValue = Math.max(0, data.getTotalContribution() + changeValue);
|
||||||
|
data.setTotalContribution(newValue);
|
||||||
|
data.setLastUpdateTime(System.currentTimeMillis());
|
||||||
|
data.setPlayerName(player.getName()); // 同步最新玩家名称(防止改名后显示异常)
|
||||||
|
|
||||||
|
// 添加贡献记录
|
||||||
|
data.getRecords().add(new ContributionRecord(type, changeValue, reason));
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
saveData();
|
||||||
|
|
||||||
|
// 日志输出
|
||||||
|
String action = changeValue > 0 ? "添加" : "扣除";
|
||||||
|
plugin.getLogger().info(action + "玩家 " + player.getName() + " 贡献值:" + Math.abs(changeValue) +
|
||||||
|
",类型:" + type.name() + ",原因:" + reason + ",当前总贡献:" + newValue);
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
private PlayerContribution getOrCreatePlayerData(@NotNull Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
String uuidStr = uuid.toString();
|
||||||
|
|
||||||
|
// 从缓存获取,不存在则创建新数据
|
||||||
|
return contributionMap.computeIfAbsent(uuidStr, k -> new PlayerContribution(uuidStr, player.getName()));
|
||||||
|
}
|
||||||
|
private Map<String, PlayerContribution> loadData() {
|
||||||
|
// 文件不存在则创建空文件
|
||||||
|
if (!contributionFile.exists()) {
|
||||||
|
createFile(contributionFile);
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(contributionFile)) {
|
||||||
|
Type mapType = new TypeToken<HashMap<String, PlayerContribution>>() {}.getType();
|
||||||
|
Map<String, PlayerContribution> loadedMap = gson.fromJson(reader, mapType);
|
||||||
|
// 防止文件损坏导致的空指针
|
||||||
|
return loadedMap != null ? loadedMap : new HashMap<>();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("加载贡献值文件失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void saveData() {
|
||||||
|
try (FileWriter writer = new FileWriter(contributionFile)) {
|
||||||
|
gson.toJson(contributionMap, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("保存贡献值文件失败!" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void createFile(File file) {
|
||||||
|
try {
|
||||||
|
file.getParentFile().mkdirs(); // 递归创建父目录(插件data目录)
|
||||||
|
file.createNewFile(); // 创建文件
|
||||||
|
plugin.getLogger().info("创建贡献值数据文件:" + file.getPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("创建贡献值文件失败!" + file.getPath() + " - " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int getContributionByName(String playerName) {
|
||||||
|
for (PlayerContribution data : contributionMap.values()) {
|
||||||
|
if (data.getPlayerName().equalsIgnoreCase(playerName)) {
|
||||||
|
return data.getTotalContribution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,57 @@ commands:
|
|||||||
shop:
|
shop:
|
||||||
description: to open the shop
|
description: to open the shop
|
||||||
usage: /<command>
|
usage: /<command>
|
||||||
|
reg:
|
||||||
|
description: to register an account
|
||||||
|
usage: /<command> <password> <password>
|
||||||
|
pc:
|
||||||
|
description: chat in a party
|
||||||
|
usage: /<command> <message>
|
||||||
|
login:
|
||||||
|
description: to login
|
||||||
|
usage: /<command> <password>
|
||||||
|
party:
|
||||||
|
description: to create a party
|
||||||
|
usage: /<command> <create|quit|join|list|kick|ban|disband|help> <args>
|
||||||
|
inventory:
|
||||||
|
description: check inventory of a player
|
||||||
|
usage: /<command> <player>
|
||||||
|
permission: permission.settag
|
||||||
|
permission-message: 无权执行命令
|
||||||
|
msg:
|
||||||
|
description: private chat with friend
|
||||||
|
usage: /<command> <player> <message>
|
||||||
|
qd:
|
||||||
|
description: qian dao
|
||||||
|
usage: /<command>
|
||||||
|
friend:
|
||||||
|
description: add friend
|
||||||
|
usage: /<command> <add|allow|deny|remove> <player>
|
||||||
|
gift:
|
||||||
|
description: gift
|
||||||
|
usage: /<command>
|
||||||
|
aboutme:
|
||||||
|
description: about you
|
||||||
|
usage: /<command>
|
||||||
|
point:
|
||||||
|
description: change point
|
||||||
|
usage: /<command> <player> <set|add|remove> <number>
|
||||||
|
land:
|
||||||
|
description: change point
|
||||||
|
usage: /<command> <player> <set|add|remove> <number>
|
||||||
|
home:
|
||||||
|
description: self kill
|
||||||
|
usage: /<command>
|
||||||
|
notice:
|
||||||
|
description: notice a player
|
||||||
|
usage: /<command> <player>
|
||||||
|
messageboard:
|
||||||
|
description: 留言板命令
|
||||||
|
usage: /<command> [add|list|support]
|
||||||
|
aliases: [ mb ]
|
||||||
|
mb:
|
||||||
|
description: 留言板命令
|
||||||
|
usage: /<command> [add|list|support]
|
||||||
permissions:
|
permissions:
|
||||||
permission.settag:
|
permission.settag:
|
||||||
description: Allows setting player tags
|
description: Allows setting player tags
|
||||||
@@ -49,3 +100,6 @@ permissions:
|
|||||||
permission.handle:
|
permission.handle:
|
||||||
description: handle players reported
|
description: handle players reported
|
||||||
default: op
|
default: op
|
||||||
|
messageboard.admin:
|
||||||
|
description: admin
|
||||||
|
default: op
|
||||||
Reference in New Issue
Block a user