feat: norp系统和配置

This commit is contained in:
叁玖领域 2026-03-19 02:38:09 +08:00
parent 81dbc80a45
commit cd5689b4a6
9 changed files with 575 additions and 6 deletions

View File

@ -19,13 +19,16 @@ package top.r3944realms.eroticdungeongame;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.bernie.geckolib.GeckoLib;
import top.r3944realms.eroticdungeongame.config.EDGConfig;
import top.r3944realms.eroticdungeongame.content.recipe.EDGRecipeBookTypes;
import top.r3944realms.eroticdungeongame.content.recipe.EDGRecipeTypeCategories;
import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler;
@ -58,6 +61,7 @@ public class EroticDungeon {
EDGRecipeBookTypes.init();
EDGRecipeTypeCategories.init();
EDGNetworkHandler.register();
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, EDGConfig.SPEC);
}
@Contract("_ -> new")
public static @NotNull ResourceLocation rl(String path) {

View File

@ -19,6 +19,7 @@ package top.r3944realms.eroticdungeongame.api.capability;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.AABB;
import org.intellij.lang.annotations.MagicConstant;
/**
* The interface Player dungeon data.
@ -79,4 +80,43 @@ public interface IPlayerDungeonData {
* @param eyeHeight the eye height
*/
void setEyeHeight(float eyeHeight);
int INVALID = -1;
/**
* The constant IDLE.
*/
int IDLE = 0;
/**
* The constant REQUEST.
*/
int REQUEST = 1;
/**
* Sets norp state.
*
* @param state the state
*/
void setNorpState(@MagicConstant(intValues = {INVALID, IDLE, REQUEST}) int state);
/**
* Gets norp state.
*
* @return the norp state
*/
int getNorpState();
/**
* Sets norp time.
*
* @param norpTime the norp time
*/
void setNorpTime(long norpTime);
/**
* Gets norp time.
*
* @return the norp time
*/
long getNorpTime();
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2025-2026 R3944Realms
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.r3944realms.eroticdungeongame.config;
import net.minecraftforge.common.ForgeConfigSpec;
public class EDGConfig {
//todo: 配置
// 是否开启norp
// norp间隔时间
// norp成功后冷却时间
// 爱机是否开启伤害
// 爱机伤害系数
public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
/**
* The constant SPEC.
*/
public static final ForgeConfigSpec SPEC;
/**
* The constant COMMON.
*/
public static final Common COMMON;
static {
BUILDER.comment("EroticDungeon Common Configuration / 通用配置").push("EroticDungeon");
COMMON = new Common(BUILDER);
BUILDER.pop();
SPEC = BUILDER.build();
}
public static class Common {
// NORP相关配置
public final ForgeConfigSpec.BooleanValue ENABLE_NORP;
public final ForgeConfigSpec.IntValue NORP_INTERVAL_TIME;
public final ForgeConfigSpec.IntValue NORP_SUCCESSFUL_COOLDOWN;
// 爱机相关配置
public final ForgeConfigSpec.BooleanValue ENABLE_LOVE_MACHINE_DAMAGE;
public final ForgeConfigSpec.DoubleValue LOVE_MACHINE_DAMAGE_FACTOR;
public Common(ForgeConfigSpec.Builder builder) {
// NORP配置组 - 中英文说明
builder.comment(
"NORP (No Role Play) Mode Settings / NORP非角色扮演模式设置",
"----------------------------------------",
"EN: Settings related to No Role Play mode functionality",
"CN: 与非角色扮演模式功能相关的设置"
).push("norp_settings");
ENABLE_NORP = builder
.comment(
"EN: Enable No Role Play mode",
" If set to true, NORP mode will be enabled",
" Default: true",
"",
"CN: 启用非角色扮演模式",
" 如果设为true将启用NORP模式",
" 默认值: true"
)
.define("enableNorp", true);
NORP_INTERVAL_TIME = builder
.comment(
"EN: NORP interval time (in seconds)",
" Time between NORP interactions",
" Default: 30 seconds",
" Min: 5, Max: 3600",
"",
"CN: NORP间隔时间",
" NORP交互之间的间隔时间",
" 默认值: 30秒",
" 最小值: 5, 最大值: 3600"
)
.defineInRange("norpIntervalTime", 30, 5, 3600);
NORP_SUCCESSFUL_COOLDOWN = builder
.comment(
"EN: NORP cooldown time after successful interaction (in seconds)",
" Time before another NORP interaction can occur",
" Default: 60 seconds",
" Min: 10, Max: 7200",
"",
"CN: NORP成功交互后的冷却时间",
" 进行下一次NORP交互前需要等待的时间",
" 默认值: 60秒",
" 最小值: 10, 最大值: 7200"
)
.defineInRange("norpSuccessfulCooldown", 60, 10, 7200);
builder.pop(); // 退出NORP配置组
// 爱机配置组 - 中英文说明
builder.comment(
"Love Machine Settings / 爱机设置",
"----------------------------------------",
"EN: Settings related to Love Machine functionality",
"CN: 与爱机功能相关的设置"
).push("love_machine_settings");
ENABLE_LOVE_MACHINE_DAMAGE = builder
.comment(
"EN: Enable Love Machine damage",
" If set to true, love machines can deal damage",
" Default: false",
"",
"CN: 启用爱机伤害",
" 如果设为true爱机可以造成伤害",
" 默认值: false"
)
.define("enableLoveMachineDamage", false);
LOVE_MACHINE_DAMAGE_FACTOR = builder
.comment(
"EN: Love Machine damage factor",
" Damage multiplier for love machines",
" Default: 1.0",
" Min: 0.1, Max: 10.0",
"",
"CN: 爱机伤害系数",
" 爱机的伤害倍率",
" 默认值: 1.0",
" 最小值: 0.1, 最大值: 10.0"
)
.defineInRange("loveMachineDamageFactor", 1.0, 0.1, 10.0);
builder.pop(); // 退出爱机配置组
// 可选添加更多配置组
builder.comment(
"General Settings / 通用设置",
"----------------------------------------",
"EN: General mod settings",
"CN: 模组通用设置"
).push("general_settings");
// 这里可以添加其他通用配置
builder.pop(); // 退出通用配置组
}
}
}

View File

@ -47,6 +47,7 @@ import software.bernie.geckolib.util.GeckoLibUtil;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.api.event.LoveMachineTickEvent;
import top.r3944realms.eroticdungeongame.client.util.RendererUtil;
import top.r3944realms.eroticdungeongame.config.EDGConfig;
import top.r3944realms.eroticdungeongame.content.EDGDamageTypes;
import top.r3944realms.eroticdungeongame.content.block.type.LoveMachineBlock;
import top.r3944realms.eroticdungeongame.core.register.EDGBlockEntities;
@ -131,20 +132,20 @@ public class LoveMachineBlockEntity extends BlockEntity implements GeoBlockEntit
ACTION.getOrDefault(loveMachineBlock.type, SpecialAction.EMPTY).doTick(pLevel, pBlockEntity, entity, pPos, pState, pLevel.getGameTime());
if (pBlockEntity.speed < 0.05f) {
if (pLevel.getGameTime() % 100 == 0) {
if (pBlockEntity.hasLubricant()) {
if (pBlockEntity.hasLubricant() || !EDGConfig.COMMON.ENABLE_LOVE_MACHINE_DAMAGE.get()) {
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addEffect(new MobEffectInstance(MobEffects.HEAL, 20, 1, true, true, true));
livingEntity.addEffect(new MobEffectInstance(MobEffects.DIG_SPEED, 20, 1, true, true, true));
}
} else entity.hurt(EDGDamageTypes.causeFuckedDamage(pLevel.registryAccess()), calcAnimSpeed(pBlockEntity.speed) * 10f);
} else entity.hurt(EDGDamageTypes.causeFuckedDamage(pLevel.registryAccess()), (float) (calcAnimSpeed(pBlockEntity.speed) * EDGConfig.COMMON.LOVE_MACHINE_DAMAGE_FACTOR.get() * 10f));
}
} else {
if (pBlockEntity.hasLubricant()) {
if (pBlockEntity.hasLubricant() || !EDGConfig.COMMON.ENABLE_LOVE_MACHINE_DAMAGE.get()) {
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addEffect(new MobEffectInstance(MobEffects.HEAL, 20, 1, true, true, true));
livingEntity.addEffect(new MobEffectInstance(MobEffects.DIG_SPEED, 20, 1, true, true, true));
}
} else entity.hurt(EDGDamageTypes.causeFuckedDamage(pLevel.registryAccess()), calcAnimSpeed(pBlockEntity.speed));
} else entity.hurt(EDGDamageTypes.causeFuckedDamage(pLevel.registryAccess()), (float) (calcAnimSpeed(pBlockEntity.speed) * EDGConfig.COMMON.LOVE_MACHINE_DAMAGE_FACTOR.get()));
}
}

View File

@ -42,12 +42,14 @@ import net.minecraftforge.common.util.FakePlayerFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.api.workspace.Services;
import top.r3944realms.eroticdungeongame.config.EDGConfig;
import top.r3944realms.eroticdungeongame.content.block.ISeatBlock;
import top.r3944realms.eroticdungeongame.content.block.blockentity.BaseSeatBlockEntity;
import top.r3944realms.eroticdungeongame.content.entity.SeatEntity;
import top.r3944realms.eroticdungeongame.content.item.DeviceKeyItem;
import top.r3944realms.eroticdungeongame.content.item.FilterItem;
import top.r3944realms.eroticdungeongame.content.util.FurnitureHelper;
import top.r3944realms.eroticdungeongame.core.service.NorpService;
import top.r3944realms.eroticdungeongame.core.service.SeatService;
import top.r3944realms.lib39.core.command.ICommandHelpManager;
import top.r3944realms.lib39.core.command.SimpleHelpCommand;
@ -87,6 +89,7 @@ public class EDGCommand extends SimpleHelpCommand {
private void registerCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
// edg eroticdungeongame
// edg help toggle <hash>
// edg norp
// edg device bind <pos> [player] [code] [player] (没有则命令执行者) 绑定在<pos>处的设备上 (如果有[code] 则顺便上锁)
// unbind <pos> 解绑<pos>处的设备
// [player] 解绑<player> (没有则命令执行者)
@ -114,6 +117,10 @@ public class EDGCommand extends SimpleHelpCommand {
// reset 重置
getRoot()
.executes(this::handleHelp)
.then(
Commands.literal("norp")
.executes(this::edg$norp)
)
.then(
Commands.literal("device")
.requires(source -> source.hasPermission(2))
@ -341,6 +348,9 @@ public class EDGCommand extends SimpleHelpCommand {
commandHelpManager.registerCommands(builder -> builder.root("edg", "commands.eroticdungeongame.root")
.expanded(true)
.branch("norp", "commands.help.eroticdungeongame.norp", norpBuilder -> {
norpBuilder.leaf("norp", "commands.help.eroticdungeongame.norp.desc");
})
// device 命令分支
.branch("device", "commands.help.eroticdungeongame.device", stack -> stack.hasPermission(2), deviceBuilder -> {
deviceBuilder.expanded(false)
@ -486,7 +496,15 @@ public class EDGCommand extends SimpleHelpCommand {
);
}
private int edg$norp(@NotNull CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
//todo: 完善下内容
CommandSourceStack source = context.getSource();
ServerPlayer player = source.getPlayerOrException();
if (EDGConfig.COMMON.ENABLE_NORP.get()) {
boolean norp = NorpService.norp(player);
}
return 1;
}
// ============ Device Bind Methods ============
private int edg$device$bind$pos$self(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
BlockPos pos = BlockPosArgument.getBlockPos(context, "pos");

View File

@ -50,11 +50,13 @@ public sealed abstract class AbstractPlayerDungeonData extends NBTEntitySyncData
setPlayerBoundingBox(null);
AABB aabb = player.getDimensions(player.getPose()).makeBoundingBox(player.position());
player.setBoundingBox(aabb);
setNorpTime(INVALID);
}
private void setDungeonData_(@NotNull SeatEntity seat, @NotNull ISeatType type) {
setEyeHeight(type.getEyeHeight());
setPlayerBoundingBox(type.getPlayerBB());
setDeviceMainBlockPos(seat.getLinkedBlockPos());
setNorpState(IDLE);
}
}

View File

@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.eroticdungeongame.core.event.CommonHandler;
import top.r3944realms.lib39.util.nbt.NBTReader;
@ -35,12 +36,17 @@ public final class PlayerDungeonData extends AbstractPlayerDungeonData {
public static final String SEAT_ANIMATION = "seat_animation";
public static final String CURRENT_BB = "current_bb";
public static final String CURRENT_EYE_HEIGHT = "current_eye_height";
public static final String NORP_STATE = "norp_state";
public static final String NORP_TIME = "norp_time";
public static final AABB ZERO = AABB.ofSize(Vec3.ZERO, 0, 0, 0);
public Player player;
public BlockPos pos;
public ResourceLocation anim;
public AABB currentBB;
public float currentEyeHeight;
public long norpTime;
@MagicConstant(intValues = {INVALID, IDLE, REQUEST})
public int norpState;
public PlayerDungeonData(Player player) {
super(CommonHandler.Game.DUNGEON_SYNC);
@ -54,6 +60,8 @@ public final class PlayerDungeonData extends AbstractPlayerDungeonData {
.stringIf(SEAT_ANIMATION, anim != null, () -> anim.toString())
.compoundIf(CURRENT_BB, currentBB != null, () -> writeBB(currentBB))
.floatValue(CURRENT_EYE_HEIGHT, currentEyeHeight, -1)
.longValue(NORP_TIME, norpTime, -1)
.intValue(NORP_STATE, norpState, INVALID)
.build();
}
@ -92,7 +100,9 @@ public final class PlayerDungeonData extends AbstractPlayerDungeonData {
}
});
}, null)
.floatValue(CURRENT_EYE_HEIGHT, this::setEyeHeight, -1.0f);
.floatValue(CURRENT_EYE_HEIGHT, this::setEyeHeight, -1.0f)
.intValue(NORP_STATE, this::setNorpState)
.longValue(NORP_TIME, this::setNorpTime);
}
@ -144,4 +154,26 @@ public final class PlayerDungeonData extends AbstractPlayerDungeonData {
this.currentEyeHeight = eyeHeight;
markDirty();
}
@Override
public void setNorpState(int state) {
this.norpState = state;
markDirty();
}
@Override
public int getNorpState() {
return norpState;
}
@Override
public void setNorpTime(long norpTime) {
this.norpTime = norpTime;
markDirty();
}
@Override
public long getNorpTime() {
return norpTime;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2025-2026 R3944Realms
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.r3944realms.eroticdungeongame.core.service;
import net.minecraft.world.entity.player.Player;
import top.r3944realms.eroticdungeongame.api.capability.IPlayerDungeonData;
import top.r3944realms.eroticdungeongame.api.workspace.Services;
import top.r3944realms.eroticdungeongame.config.EDGConfig;
import top.r3944realms.eroticdungeongame.core.capability.PlayerDungeonDataProvider;
import top.r3944realms.eroticdungeongame.util.IEDGEntity;
public class NorpService {
public static boolean checkCanNorp(Player player) {
return Services.WORK_SPACE.isInDevice(player);
}
public static boolean norp(Player player) {
if (checkCanNorp(player))
return player.getCapability(PlayerDungeonDataProvider.PLAYER_DUNGEON_DATA_CAP)
.map(data -> {
boolean flag = false;
switch (data.getNorpState()) {
case IPlayerDungeonData.IDLE -> {
flag = System.currentTimeMillis() - data.getNorpTime() >= EDGConfig.COMMON.NORP_SUCCESSFUL_COOLDOWN.get() * 1000;
if (flag) {
data.setNorpState(data.REQUEST);
//todo 提醒玩家下次生效
} else {
// todo 还在冷却中
}
}
case IPlayerDungeonData.REQUEST -> {
flag = System.currentTimeMillis() - data.getNorpTime() >= EDGConfig.COMMON.NORP_INTERVAL_TIME.get() * 1000;
if (flag) {
data.setNorpState(data.IDLE);
((IEDGEntity)player).releaseEntityFromEDGDevice(true);
//todo 提醒玩家成功
} else {
// todo 提醒玩家等待间隔
}
}
default -> {}
}
return flag;
}).orElse(false);
else return false;
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright 2025-2026 R3944Realms
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.r3944realms.eroticdungeongame.util;
import net.minecraftforge.fml.loading.FMLPaths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.locks.ReentrantLock;
/**
* NORP Logger - 简单的日志记录工具
* 仅在调用时检查并写入日志自动按天轮换
*/
public class NorpLogger {
private static final Logger LOGGER = LoggerFactory.getLogger("NorpLogger");
// 日志文件配置
private static final String LOG_DIR = "norp";
private static final String LOG_FILE_PREFIX = "norp_";
private static final String LOG_FILE_SUFFIX = ".log";
private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
// 日志级别枚举
public enum LogLevel {
INFO, WARNING, ERROR, DEBUG, SUCCESS
}
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
private static final DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern(TIMESTAMP_FORMAT);
private static final ReentrantLock lock = new ReentrantLock();
// 当前日志文件信息缓存
private static Path currentLogPath = null;
private static String currentDateStr = null;
/**
* 私有构造函数 - 工具类不需要实例化
*/
private NorpLogger() {}
/**
* 获取今天的日志文件路径
*/
private static Path getTodayLogPath() {
Path mcPath = FMLPaths.GAMEDIR.get();
String today = LocalDate.now().format(dateFormatter);
String fileName = LOG_FILE_PREFIX + today + LOG_FILE_SUFFIX;
return mcPath.resolve(LOG_DIR).resolve(fileName);
}
/**
* 确保日志目录存在
*/
private static void ensureLogDirectoryExists() {
try {
Path logDir = FMLPaths.GAMEDIR.get().resolve(LOG_DIR);
if (!Files.exists(logDir)) {
Files.createDirectories(logDir);
}
} catch (IOException e) {
LOGGER.error("Failed to create log directory", e);
}
}
/**
* 检查是否需要切换日志文件
*/
private static boolean shouldRotateLog() {
String today = LocalDate.now().format(dateFormatter);
return !today.equals(currentDateStr) || currentLogPath == null;
}
/**
* 获取当前应该写入的文件路径
*/
private static Path getCurrentLogPath() {
lock.lock();
try {
if (shouldRotateLog()) {
currentDateStr = LocalDate.now().format(dateFormatter);
currentLogPath = getTodayLogPath();
ensureLogDirectoryExists();
}
return currentLogPath;
} finally {
lock.unlock();
}
}
/**
* 写入日志到文件
*/
private static void writeToFile(String content) {
Path logPath = getCurrentLogPath();
lock.lock();
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(logPath.toFile(), true),
StandardCharsets.UTF_8
)
)) {
writer.write(content);
writer.newLine();
writer.flush();
} catch (IOException e) {
LOGGER.error("Failed to write to log file: {}", logPath, e);
} finally {
lock.unlock();
}
}
/**
* 格式化日志消息
*/
private static String formatMessage(String level, String message, Object... args) {
String formattedMsg = args.length > 0 ? String.format(message, args) : message;
return String.format("%s [%s] %s",
LocalDateTime.now().format(timestampFormatter),
level,
formattedMsg);
}
/**
* 记录日志核心方法
*/
public static void log(String level, String message, Object... args) {
String formattedMessage = formatMessage(level, message, args);
writeToFile(formattedMessage);
}
/**
* 使用LogLevel枚举记录日志
*/
public static void log(LogLevel level, String message, Object... args) {
log(level.name(), message, args);
}
/**
* 记录INFO级别日志
*/
public static void info(String message, Object... args) {
log(LogLevel.INFO, message, args);
}
/**
* 记录WARNING级别日志
*/
public static void warning(String message, Object... args) {
log(LogLevel.WARNING, message, args);
}
/**
* 记录ERROR级别日志
*/
public static void error(String message, Object... args) {
log(LogLevel.ERROR, message, args);
}
/**
* 记录DEBUG级别日志
*/
public static void debug(String message, Object... args) {
log(LogLevel.DEBUG, message, args);
}
/**
* 记录SUCCESS级别日志
*/
public static void success(String message, Object... args) {
log(LogLevel.SUCCESS, message, args);
}
/**
* 记录NORP事件
*/
public static void logNorpEvent(String playerName, String action, String result) {
info("NORP Event - Player: %s, Action: %s, Result: %s", playerName, action, result);
}
/**
* 记录NORP成功
*/
public static void logNorpSuccess(String playerName, String action) {
success("NORP Success - Player: %s, Action: %s", playerName, action);
}
/**
* 记录NORP失败
*/
public static void logNorpFailure(String playerName, String action, String reason) {
warning("NORP Failure - Player: %s, Action: %s, Reason: %s", playerName, action, reason);
}
/**
* 记录带标题的分隔线
*/
public static void logSeparator(String title) {
String separator = "════════════════════════════════════════════";
info("%s %s %s", separator, title, separator);
}
/**
* 记录空行
*/
public static void logEmptyLine() {
writeToFile("");
}
/**
* 获取当前日志文件路径
*/
public static Path getCurrentLogFilePath() {
return getCurrentLogPath();
}
/**
* 检查指定日期的日志文件是否存在
*/
public static boolean logExistsForDate(LocalDate date) {
String fileName = LOG_FILE_PREFIX + date.format(dateFormatter) + LOG_FILE_SUFFIX;
Path logPath = FMLPaths.GAMEDIR.get().resolve(LOG_DIR).resolve(fileName);
return Files.exists(logPath);
}
}