feat(lohub): Add complete configuration system with 6 config files

- Added messages.yml for all plugin messages with customizable prefix
- Added commands.yml for custom commands (discord, website, store, rules, heal, clearinventory)
- Added serverselector.yml for GUI server selector menu
- Added data.yml for persistent data storage (chat_locked, spawn location)
- Updated ConfigManager to load all config files
- Updated ChatLockManager to use data.yml instead of chatlock.dat
- Updated SpawnManager to use data.yml instead of spawn.yml
- Updated all commands to use messages.yml
- Updated listeners to use messages.yml
- Removed duplicate config files from root directory
- Added CONFIG_GUIDE.md documentation
This commit is contained in:
loki5512344 2026-02-24 13:27:18 +01:00
parent 102341bb2c
commit 1e5e8d8638
36 changed files with 1580 additions and 544 deletions

View file

@ -0,0 +1,81 @@
package com.loki.lochat.core.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.loki.lochat.data.model.MuteData;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MuteHistoryManager {
private final Map<UUID, List<MuteData.MuteHistoryEntry>> history = new ConcurrentHashMap<>();
private final File historyFile;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public MuteHistoryManager(File historyFile) {
this.historyFile = historyFile;
load();
}
public void addEntry(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
List<MuteData.MuteHistoryEntry> entries = history.computeIfAbsent(uuid, k -> new ArrayList<>());
entries.add(new MuteData.MuteHistoryEntry(
playerName,
duration,
reason,
mutedBy,
System.currentTimeMillis(),
false,
null,
0
));
}
public void updateUnmute(UUID uuid, String unmutedBy) {
List<MuteData.MuteHistoryEntry> entries = history.get(uuid);
if (entries != null && !entries.isEmpty()) {
MuteData.MuteHistoryEntry lastEntry = entries.get(entries.size() - 1);
if (lastEntry.unmutedBy == null) {
lastEntry.unmuted = true;
lastEntry.unmutedBy = unmutedBy;
lastEntry.unmutedAt = System.currentTimeMillis();
}
}
}
public List<MuteData.MuteHistoryEntry> getHistory(UUID uuid) {
return history.getOrDefault(uuid, Collections.emptyList());
}
public void save() {
try (FileWriter writer = new FileWriter(historyFile)) {
gson.toJson(history, writer);
} catch (IOException e) {
e.printStackTrace();
}
}
private void load() {
if (!historyFile.exists()) {
return;
}
try (FileReader reader = new FileReader(historyFile)) {
Type type = new TypeToken<Map<UUID, List<MuteData.MuteHistoryEntry>>>() {
}.getType();
Map<UUID, List<MuteData.MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
history.putAll(loaded);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -6,6 +6,7 @@ import com.google.gson.reflect.TypeToken;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.utils.TimeFormatter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
@ -14,23 +15,19 @@ import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса мутов
*/
public class MuteServiceImpl implements MuteService {
private final JavaPlugin plugin;
private final Map<UUID, MuteData> mutes = new ConcurrentHashMap<>();
private final Map<UUID, List<MuteData.MuteHistoryEntry>> history = new ConcurrentHashMap<>();
private final MuteHistoryManager historyManager;
private final File dataFile;
private final File historyFile;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public MuteServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "mutes.json");
this.historyFile = new File(plugin.getDataFolder(), "mute-history.json");
this.historyManager = new MuteHistoryManager(new File(plugin.getDataFolder(), "mute-history.json"));
load();
loadHistory();
}
@Override
@ -39,9 +36,7 @@ public class MuteServiceImpl implements MuteService {
MuteData data = new MuteData(uuid, playerName, endTime, reason, mutedBy);
mutes.put(uuid, data);
// Добавляем в историю
addToHistory(uuid, playerName, duration, reason, mutedBy);
historyManager.addEntry(uuid, playerName, duration, reason, mutedBy);
saveAsync();
}
@ -49,7 +44,7 @@ public class MuteServiceImpl implements MuteService {
public boolean unmute(UUID uuid, String unmutedBy) {
MuteData removed = mutes.remove(uuid);
if (removed != null) {
updateHistoryUnmute(uuid, unmutedBy);
historyManager.updateUnmute(uuid, unmutedBy);
saveAsync();
return true;
}
@ -59,9 +54,11 @@ public class MuteServiceImpl implements MuteService {
@Override
public boolean isMuted(UUID uuid) {
MuteData data = mutes.get(uuid);
if (data == null) return false;
if (data == null) {
return false;
}
if (data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime()) {
if (isExpired(data)) {
mutes.remove(uuid);
saveAsync();
return false;
@ -71,66 +68,35 @@ public class MuteServiceImpl implements MuteService {
@Override
public MuteData getMuteData(UUID uuid) {
if (!isMuted(uuid)) return null;
return mutes.get(uuid);
return isMuted(uuid) ? mutes.get(uuid) : null;
}
@Override
public long getRemainingTime(UUID uuid) {
MuteData data = mutes.get(uuid);
if (data == null) return 0;
if (data.getEndTime() == 0) return -1; // Перманентный
if (data == null) {
return 0;
}
if (data.getEndTime() == 0) {
return -1;
}
return Math.max(0, data.getEndTime() - System.currentTimeMillis());
}
@Override
public String formatTime(long millis) {
if (millis <= 0) return "0с";
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {
return hours + "ч " + (minutes % 60) + "м";
} else if (minutes > 0) {
return minutes + "м " + (seconds % 60) + "с";
} else {
return seconds + "с";
}
return TimeFormatter.format(millis);
}
@Override
public long parseTime(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) return 0;
timeStr = timeStr.toLowerCase();
long multiplier = 1000; // базово в миллисекундах
try {
if (timeStr.endsWith("d")) {
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * multiplier;
} else if (timeStr.endsWith("h")) {
return Long.parseLong(timeStr.replace("h", "")) * 60 * 60 * multiplier;
} else if (timeStr.endsWith("m")) {
return Long.parseLong(timeStr.replace("m", "")) * 60 * multiplier;
} else if (timeStr.endsWith("s")) {
return Long.parseLong(timeStr.replace("s", "")) * multiplier;
} else {
return Long.parseLong(timeStr) * 60 * multiplier;
}
} catch (NumberFormatException e) {
return -1;
}
return TimeFormatter.parse(timeStr);
}
@Override
public long getMaxDuration(Player player) {
if (player.hasPermission("lochat.mute.dur.perm")) {
return 0; // Перманентный
return 0;
}
String[] durations = {"30d", "14d", "7d", "3d", "1d", "12h", "6h", "1h", "30m", "10m"};
@ -140,7 +106,7 @@ public class MuteServiceImpl implements MuteService {
}
}
return -1; // Нет прав
return -1;
}
@Override
@ -154,37 +120,39 @@ public class MuteServiceImpl implements MuteService {
}
long maxDuration = getMaxDuration(player);
if (maxDuration == -1) return false;
if (maxDuration == 0) return true;
if (maxDuration == -1) {
return false;
}
if (maxDuration == 0) {
return true;
}
return duration <= maxDuration;
}
@Override
public List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid) {
return history.getOrDefault(uuid, new ArrayList<>());
return historyManager.getHistory(uuid);
}
@Override
public List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName) {
List<MuteData.MuteHistoryEntry> result = new ArrayList<>();
for (List<MuteData.MuteHistoryEntry> playerHistory : history.values()) {
for (MuteData.MuteHistoryEntry entry : playerHistory) {
if (entry.mutedBy != null && entry.mutedBy.equalsIgnoreCase(operatorName)) {
result.add(entry);
}
}
}
mutes.keySet().forEach(uuid -> {
historyManager.getHistory(uuid).stream()
.filter(entry -> entry.mutedBy != null && entry.mutedBy.equalsIgnoreCase(operatorName))
.forEach(result::add);
});
result.sort((a, b) -> Long.compare(b.mutedAt, a.mutedAt));
return result;
}
@Override
public UUID getUUIDByName(String name) {
for (Map.Entry<UUID, List<MuteData.MuteHistoryEntry>> entry : history.entrySet()) {
for (MuteData.MuteHistoryEntry historyEntry : entry.getValue()) {
if (historyEntry.playerName != null && historyEntry.playerName.equalsIgnoreCase(name)) {
return entry.getKey();
for (UUID uuid : mutes.keySet()) {
for (MuteData.MuteHistoryEntry entry : historyManager.getHistory(uuid)) {
if (entry.playerName != null && entry.playerName.equalsIgnoreCase(name)) {
return uuid;
}
}
}
@ -193,10 +161,7 @@ public class MuteServiceImpl implements MuteService {
@Override
public Map<UUID, MuteData> getActiveMutes() {
mutes.entrySet().removeIf(entry -> {
MuteData data = entry.getValue();
return data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime();
});
mutes.entrySet().removeIf(entry -> isExpired(entry.getValue()));
return new HashMap<>(mutes);
}
@ -212,23 +177,37 @@ public class MuteServiceImpl implements MuteService {
@Override
public void save() {
try {
if (!dataFile.getParentFile().exists()) {
dataFile.getParentFile().mkdirs();
}
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(dataFile)) {
gson.toJson(toSave, writer);
}
ensureDataFolderExists();
saveMutes();
historyManager.save();
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения мутов: " + e.getMessage());
plugin.getLogger().warning("Error saving mutes: " + e.getMessage());
}
}
private boolean isExpired(MuteData data) {
return data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime();
}
private void ensureDataFolderExists() {
if (!dataFile.getParentFile().exists()) {
dataFile.getParentFile().mkdirs();
}
}
private void saveMutes() throws IOException {
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(dataFile)) {
gson.toJson(toSave, writer);
}
}
private void load() {
if (!dataFile.exists()) return;
if (!dataFile.exists()) {
return;
}
try (Reader reader = new FileReader(dataFile)) {
Type type = new TypeToken<Map<String, MuteData>>() {
@ -243,86 +222,11 @@ public class MuteServiceImpl implements MuteService {
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки мутов: " + e.getMessage());
plugin.getLogger().warning("Error loading mutes: " + e.getMessage());
}
}
private void loadHistory() {
if (!historyFile.exists()) return;
try (Reader reader = new FileReader(historyFile)) {
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>() {
}.getType();
Map<String, List<MuteData.MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
history.put(UUID.fromString(key), new ArrayList<>(value));
} catch (IllegalArgumentException ignored) {
}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage());
}
}
private void saveHistory() {
try {
if (!historyFile.getParentFile().exists()) {
historyFile.getParentFile().mkdirs();
}
Map<String, List<MuteData.MuteHistoryEntry>> toSave = new ConcurrentHashMap<>();
history.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(historyFile)) {
gson.toJson(toSave, writer);
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage());
}
}
private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.computeIfAbsent(uuid, k -> new ArrayList<>());
playerHistory.add(new MuteData.MuteHistoryEntry(
playerName,
duration,
reason,
mutedBy,
System.currentTimeMillis(),
false,
null,
0
));
saveHistoryAsync();
}
private void updateHistoryUnmute(UUID uuid, String unmutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.get(uuid);
if (playerHistory != null && !playerHistory.isEmpty()) {
for (int i = playerHistory.size() - 1; i >= 0; i--) {
MuteData.MuteHistoryEntry entry = playerHistory.get(i);
if (!entry.unmuted) {
entry.unmuted = true;
entry.unmutedBy = unmutedBy;
entry.unmutedAt = System.currentTimeMillis();
break;
}
}
}
saveHistoryAsync();
}
private void saveHistoryAsync() {
FoliaUtil.runAsync(plugin, this::saveHistory);
}
private void saveAsync() {
FoliaUtil.runAsync(plugin, () -> {
save();
saveHistory();
});
FoliaUtil.runAsync(plugin, this::save);
}
}

View file

@ -0,0 +1,59 @@
package com.loki.lochat.utils;
public final class TimeFormatter {
private TimeFormatter() {
}
public static String format(long millis) {
if (millis <= 0) {
return "0с";
}
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {
return hours + "ч " + (minutes % 60) + "м";
} else if (minutes > 0) {
return minutes + "м " + (seconds % 60) + "с";
} else {
return seconds + "с";
}
}
public static long parse(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) {
return 0;
}
long totalMillis = 0;
StringBuilder number = new StringBuilder();
for (char c : timeStr.toCharArray()) {
if (Character.isDigit(c)) {
number.append(c);
} else if (number.length() > 0) {
long value = Long.parseLong(number.toString());
totalMillis += getMillisForUnit(c, value);
number.setLength(0);
}
}
return totalMillis;
}
private static long getMillisForUnit(char unit, long value) {
return switch (Character.toLowerCase(unit)) {
case 's', 'с' -> value * 1000;
case 'm', 'м' -> value * 60 * 1000;
case 'h', 'ч' -> value * 60 * 60 * 1000;
case 'd', 'д' -> value * 24 * 60 * 60 * 1000;
default -> 0;
};
}
}