This commit is contained in:
BRanulf_Explode 2025-08-04 15:11:07 +08:00
parent 245103cf44
commit 98b04f7155
25 changed files with 264 additions and 28 deletions

View File

@ -14,7 +14,9 @@ preprocess {
def mc12105 = createNode("1.21.5", 1_21_05, "")
def mc12106 = createNode("1.21.6", 1_21_06, "")
def mc12107 = createNode("1.21.7", 1_21_07, "")
def mc12108 = createNode("1.21.8", 1_21_08, "")
mc12108.link(mc12107, file("versions/mapping_12108_12107.txt"))
mc12107.link(mc12106, file("versions/mapping_12107_12106.txt"))
mc12106.link(mc12105, file("versions/mapping_12106_12105.txt"))
mc12105.link(mc12104, file("versions/mapping_12105_12104.txt"))

View File

@ -32,6 +32,12 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
include modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
modImplementation "me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}"
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
implementation 'com.google.code.gson:gson:2.8.9'
}

View File

@ -8,7 +8,7 @@ loader_version=0.16.10
# Mod Properties
mod_name=Disc Jockey Revive
mod_id=disc_jockey_revive
mod_version=1.14.514.144
mod_version=1.14.514.148
maven_group=org.example1
archives_base_name=ServerPlayerOnlineTracker
# Dependencies

View File

@ -3,6 +3,7 @@
"1.21.4",
"1.21.5",
"1.21.6",
"1.21.7"
"1.21.7",
"1.21.8"
]
}

View File

@ -0,0 +1,46 @@
package org.branulf.playertime;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import org.branulf.playertime.ModConfig;
public class ClothConfigScreen {
public static Screen create(Screen parent, ModConfig config) {
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(Text.translatable("title.playertime.config"))
.setSavingRunnable(config::saveConfig);
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
ConfigCategory general = builder.getOrCreateCategory(Text.translatable("category.playertime.general"));
// 端口
general.addEntry(entryBuilder.startIntField(Text.translatable("option.playertime.webPort"), config.webPort)
.setDefaultValue(60048)
.setMin(1024).setMax(65535)
.setTooltip(Text.translatable("tooltip.playertime.webPort"))
.setSaveConsumer(newValue -> config.webPort = newValue)
.build());
// 白名单
general.addEntry(entryBuilder.startBooleanToggle(Text.translatable("option.playertime.whitelistOnly"), ModConfig.whitelistOnly)
.setDefaultValue(true)
.setTooltip(Text.translatable("tooltip.playertime.whitelistOnly"))
.setSaveConsumer(newValue -> ModConfig.whitelistOnly = newValue)
.build());
// 间隔
general.addEntry(entryBuilder.startIntField(Text.translatable("option.playertime.saveIntervalMinutes"), config.saveIntervalMinutes)
.setDefaultValue(5)
.setMin(1).setMax(60)
.setTooltip(Text.translatable("tooltip.playertime.saveIntervalMinutes"))
.setSaveConsumer(newValue -> config.saveIntervalMinutes = newValue)
.build());
return builder.build();
}
}

View File

@ -1,26 +1,39 @@
package org.branulf.playertime;
import com.google.gson.*;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.nio.file.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ModConfig {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final Path configPath;
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public final Path configPath;
// 配置项
private int webPort = 60048;
private String language = "zh_cn";
private boolean whitelistOnly = true; // 默认只记录白名单玩家
private int saveIntervalMinutes = 5; // 默认每5分钟保存一次
public int webPort = 60048;
public String language = "zh_cn";
public static boolean whitelistOnly = true; // 默认只记录白名单玩家
public int saveIntervalMinutes = 5; // 默认每5分钟保存一次
public ModConfig(Path configDir) {
this.configPath = configDir.resolve("playertime-config.json");
public ModConfig(Path configDir) throws IOException {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
this.configPath = configDir.resolve("config").resolve("playertime-config.json");
} else {
this.configPath = configDir.resolve("playertime-config.json");
}
if (!Files.exists(this.configPath.getParent())) {
Files.createDirectories(this.configPath.getParent());
}
loadConfig();
}
// 加载配置
private void loadConfig() {
public void loadConfig() {
if (!Files.exists(configPath)) {
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件未找到,正在创建默认配置: {}", configPath);
saveConfig(); // 创建默认配置并保存
@ -97,7 +110,7 @@ public class ModConfig {
}
// 保存配置
private void saveConfig() {
public void saveConfig() {
JsonObject json = new JsonObject();
json.addProperty("webPort", webPort);
json.addProperty("language", language);
@ -113,6 +126,7 @@ public class ModConfig {
} catch (Exception e) {
PlayerTimeMod.LOGGER.error("[在线时间] 保存配置失败到 {}", configPath, e);
}
notifyConfigChanged();
}
// 获取端口
@ -122,6 +136,19 @@ public class ModConfig {
// 获取语言
public String getLanguage() {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
try {
net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance();
if (client != null) {
String clientLang = client.options.language;
if (clientLang != null && !clientLang.isEmpty()) {
return clientLang;
}
}
} catch (Exception e) {
PlayerTimeMod.LOGGER.warn("[在线时间] 无法获取客户端语言,使用配置值", e);
}
}
return language;
}
@ -134,4 +161,23 @@ public class ModConfig {
public int getSaveIntervalMinutes() {
return saveIntervalMinutes;
}
private static final List<ConfigChangeListener> listeners = new CopyOnWriteArrayList<>();
public interface ConfigChangeListener {
void onConfigChanged(ModConfig config);
}
public static void addChangeListener(ConfigChangeListener listener) {
listeners.add(listener);
}
private void notifyConfigChanged() {
for (ConfigChangeListener listener : listeners) {
listener.onConfigChanged(this);
}
}
}

View File

@ -0,0 +1,18 @@
package org.branulf.playertime;
import me.shedaniel.autoconfig.AutoConfig;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.screen.Screen;
import org.branulf.playertime.ModConfig;
import org.branulf.playertime.PlayerTimeMod;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
@Environment(EnvType.CLIENT)
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> ClothConfigScreen.create(parent, PlayerTimeMod.getConfig());
}
}

View File

@ -1,6 +1,9 @@
package org.branulf.playertime;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@ -303,7 +306,7 @@ public class PlayerTimeMod implements ModInitializer {
LOGGER.warn("[在线时间] 下载链接: {}", latestVersionLink);
LOGGER.warn("==================================================");
} else {
LOGGER.info("[在线时间] 当前已是最新版本(或检查失败)");
LOGGER.info("[在线时间] 当前已是最新版本");
}
} catch (javax.xml.parsers.ParserConfigurationException e) {
@ -356,4 +359,11 @@ public class PlayerTimeMod implements ModInitializer {
return 0;
}
}
@Environment(EnvType.CLIENT)
public static class ClientInitializer implements ClientModInitializer {
@Override
public void onInitializeClient() {
}
}
}

View File

@ -0,0 +1,10 @@
package org.branulf.playertime;
import net.fabricmc.api.ClientModInitializer;
public class PlayerTimeModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
PlayerTimeMod.LOGGER.info("[在线时间] 客户端初始化完成");
}
}

View File

@ -2,6 +2,8 @@ package org.branulf.playertime;
import com.google.gson.*;
import com.mojang.authlib.GameProfile;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
@ -25,7 +27,13 @@ public class PlayerTimeTracker {
public PlayerTimeTracker(MinecraftServer server, ModConfig config) {
this.server = server;
this.config = config;
this.dataFile = server.getRunDirectory().resolve("player_time_data.json");
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
this.dataFile = FabricLoader.getInstance().getGameDir().resolve("saves")
.resolve(server.getSaveProperties().getLevelName())
.resolve("player_time_data.json");
} else {
this.dataFile = server.getRunDirectory().resolve("player_time_data.json");
}
}
public void loadData() {
@ -136,9 +144,11 @@ public class PlayerTimeTracker {
long now = Instant.now().getEpochSecond();
List<PlayerTimeStatsWithNames> statsList = new ArrayList<>();
boolean shouldCategorize = config.isWhitelistOnly();
// 获取白名单 UUID 集合 (如果需要过滤)
Set<UUID> whitelistUuids;
if (config.isWhitelistOnly()) {
if (shouldCategorize) {
whitelistUuids = new HashSet<>();
if (server != null && server.getPlayerManager() != null && server.getUserCache() != null) {
for (String name : server.getPlayerManager().getWhitelist().getNames()) {

View File

@ -16,6 +16,8 @@ import java.util.concurrent.*;
import java.util.stream.Collectors;
// 导入API类别忘了
import net.minecraft.server.PlayerManager;
import net.minecraft.server.Whitelist;
import org.branulf.playertime.api.PlayerStatsResponse;
import org.branulf.playertime.api.ServerStatusResponse;
import org.branulf.playertime.api.OnlinePlayersResponse;
@ -230,6 +232,9 @@ public class WebServer {
status.uptime = uptimeMillis / 1000; //
status.uptime_formatted = formatUptime(uptimeMillis);
status.configWhitelistOnly = ModConfig.whitelistOnly;
status.serverWhitelistEnabled = minecraftServer.getPlayerManager().isWhitelistEnabled();
try {
File diskPartition = new File(".");
status.disk.total = diskPartition.getTotalSpace();

View File

@ -10,6 +10,8 @@ public class ServerStatusResponse {
public long uptime; //
public String uptime_formatted;
public ServerInfo server = new ServerInfo();
public boolean configWhitelistOnly;
public boolean serverWhitelistEnabled;
public static class MemoryStats {
public long max; // 字节

View File

@ -12,7 +12,7 @@
"playertime.command.error.player_only": "This command can only be executed by a player.",
"playertime.web.title": "Player online and status statistics",
"playertime.web.warning": "Data tracking starts from the mod installation time and does not include any data before installation.",
"playertime.web.warning": "Note: Data tracking starts from the mod installation time and does not include any data before installation.",
"playertime.web.status.online_players": "Online Players",
"playertime.web.status.total": "Total",
"playertime.web.status.players": "Players",
@ -61,5 +61,17 @@
"playertime.web.error.load_failed": "Failed to load data, check console",
"playertime.web.footer.license": "This project is open source under the GPL3 license"
"playertime.web.footer.license": "This project is open source under the GPL3 license",
"title.playertime.config": "Player Time Tracker Config",
"category.playertime.general": "General Settings",
"option.playertime.webPort": "Web Server Port",
"tooltip.playertime.webPort": "Port for the web server (default: 60048)",
"option.playertime.language": "Language",
"tooltip.playertime.language": "Mod display language",
"option.playertime.whitelistOnly": "Whitelist Only",
"tooltip.playertime.whitelistOnly": "Only track whitelisted players",
"option.playertime.saveIntervalMinutes": "Save Interval (minutes)",
"tooltip.playertime.saveIntervalMinutes": "Auto-save interval for player data",
"playertime.web.warning.whitelist_disabled": "Warning: Whitelist-only tracking is enabled, but server whitelist is not active. Non-whitelisted players will be classified as non-players."
}

View File

@ -13,7 +13,7 @@
"playertime.web.title": "玩家在线及状态统计",
"playertime.web.warning": "数据统计时间开始于此MOD安装时间不包含安装之前的所有数据",
"playertime.web.warning": "注意:数据统计时间开始于此MOD安装时间不包含安装之前的所有数据",
"playertime.web.status.online_players": "在线玩家",
"playertime.web.status.total": "总数",
"playertime.web.status.players": "玩家",
@ -63,5 +63,17 @@
"playertime.web.error.load_failed": "加载数据失败,请检查控制台",
"playertime.web.footer.license": "本项目基于 GPL3许可证 开源"
"playertime.web.footer.license": "本项目基于 GPL3许可证 开源",
"title.playertime.config": "玩家在线时长配置",
"category.playertime.general": "通用设置",
"option.playertime.webPort": "Web服务器端口",
"tooltip.playertime.webPort": "Web服务器的端口默认60048",
"option.playertime.language": "语言",
"tooltip.playertime.language": "模组的显示语言",
"option.playertime.whitelistOnly": "仅记录白名单",
"tooltip.playertime.whitelistOnly": "只记录白名单上的玩家",
"option.playertime.saveIntervalMinutes": "保存间隔(分钟)",
"tooltip.playertime.saveIntervalMinutes": "自动保存数据的时间间隔",
"playertime.web.warning.whitelist_disabled": "警告:配置为仅记录白名单玩家,但服务端未启用白名单功能。非白名单玩家将被视为假人。"
}

View File

@ -8,7 +8,8 @@
--border-color: #e9ecef;
--hover-bg: #f1f3f5;
--success-color: #4CAF50;
--warning-color: #FFC107;
--warning-color: #bf9107;
--note-color: #3a56d4;
--danger-color: #FF5722;
--accent-blue: #4361ee;
@ -49,6 +50,7 @@
--hover-bg: #2d2d2d;
--success-color: #66BB6A;
--warning-color: #FFEE58;
--note-color: #4d79cf;
--danger-color: #FF8A65;
}
@ -194,6 +196,12 @@ body {
border-left: 4px solid var(--warning-color);
}
.notification.note {
background-color: rgba(7, 197, 255, 0.1);
color: var(--note-color);
border-left: 4px solid var(--note-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));

View File

@ -6,7 +6,7 @@
<title data-lang-key="playertime.web.title">玩家在线及状态统计</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="icon" href="icon.ico" type="image/x-icon">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
</head>
@ -29,9 +29,12 @@
<div class="main-content">
<!-- 警告信息 -->
<div class="notification warning" data-lang-key="playertime.web.warning">
<div class="notification note" data-lang-key="playertime.web.warning">
<i class="fas fa-exclamation-circle"></i>
<span>数据统计时间开始于此MOD安装时间不包含安装之前的所有数据</span>
<span>注意数据统计时间开始于此MOD安装时间不包含安装之前的所有数据</span>
</div>
<div class="notification warning" id="whitelist-warning" style="display: none;">
</div>
<!-- 在线玩家卡片 -->

View File

@ -165,6 +165,9 @@ document.addEventListener('DOMContentLoaded', function() {
updatePlayerCounts(playerCountsData);
updateServerStatus(serverStatusData);
if (serverStatusData.configWhitelistOnly && !serverStatusData.serverWhitelistEnabled) {
showWarning(getLangString('playertime.web.warning.whitelist_disabled'));
}
} catch (error) {
console.error('加载数据出错:', error);
showError(getLangString('playertime.web.error.load_failed'));
@ -586,4 +589,15 @@ document.addEventListener('DOMContentLoaded', function() {
element.style.color = color;
}
function showWarning(message) {
const warningEl = document.getElementById('whitelist-warning');
if (!warningEl) return;
warningEl.innerHTML = `
<i class="fas fa-exclamation-triangle"></i>
<span>${message}</span>
`;
warningEl.style.display = 'block';
}
});

View File

@ -11,17 +11,25 @@
"homepage": "https://git.branulf.top/Branulf",
"sources": "https://git.branulf.top/Branulf/ServerPlayerOnlineTracker"
},
"icon": "assets/playertime/icon540.png",
"icon": "assets/playertime/icon.ico",
"license": "GPL3",
"environment": "server",
"environment": "*",
"entrypoints": {
"main": [
"org.branulf.playertime.PlayerTimeMod"
],
"client": [
"org.branulf.playertime.PlayerTimeModClient"
],
"modmenu": [
"org.branulf.playertime.ModMenuIntegration"
]
},
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}"
"minecraft": "${minecraft_version}",
"cloth-config": "*",
"modmenu": "*"
}
}

View File

@ -1 +0,0 @@
public field net/minecraft/server/MinecraftServer workerExecutor Ljava/util/concurrent/Executor;

View File

@ -6,3 +6,6 @@ minecraft_dependency=1.21.4
game_versions=1.21.4
fabric_api_version=0.119.2+1.21.4
modmenu_version=13.0.3
cloth_config_version=17.0.144

View File

@ -6,3 +6,7 @@ minecraft_dependency=1.21.5
game_versions=1.21.5
fabric_api_version=0.119.5+1.21.5
modmenu_version=14.0.0-rc.2
cloth_config_version=18.0.145

View File

@ -6,3 +6,6 @@ minecraft_dependency=1.21.6
game_versions=1.21.6
fabric_api_version=0.127.0+1.21.6
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

View File

@ -5,4 +5,7 @@ minecraft_dependency=1.21.7
game_versions=1.21.7
fabric_api_version=0.128.1+1.21.7
fabric_api_version=0.128.1+1.21.7
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.8
yarn_mappings=1.21.8+build.1
minecraft_dependency=1.21.8
game_versions=1.21.8
fabric_api_version=0.129.0+1.21.8
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

View File