refactor WIP
This commit is contained in:
51 files changed
+1189
-544
No files matched your search
+3
-3
@@ -29,7 +29,7 @@ SFCraft 是一个 Fabric Mod,他的所有内容都是放在一起的,下列
|
||||
在此类定义的字段将会自动作为配置文件的一部分生成。你可以通过调用 `SFCraft.getInstance().getConfig()` 得到配置对象。
|
||||
- `io.ib67.sfcraft.Listener` 是事件监听器。大多数回调/事件的处理代码放在这里。
|
||||
- `io.ib67.sfcraft.Commands` 是处理命令逻辑的地方,而命令在 `SFCraft#registerCommands` 方法注册。
|
||||
- `io.ib67.sfcraft.SFConsts` 存放一些频繁使用的常量,例如权限。
|
||||
- `io.ib67.sfcraft.util.SFConsts` 存放一些频繁使用的常量,例如权限。
|
||||
- `io.ib67.sfcraft.mixin.server` 下是一些 `Mixin` 类,接下来会讲。
|
||||
|
||||
最后,`io.ib67.sfcraft.SFCraft` 是整个 Mod 的入口,也是核心部分。你可以从 `SFCraft.getInstance()` 返回的对象得到 Mod 里的大多数组件。
|
||||
@@ -152,10 +152,10 @@ public class DawnAfterLongNightEvent extends RandomEvent {
|
||||
|
||||
## 聊天修改
|
||||
|
||||
类似 `.xyz`, `@xx` 的功能都位于 `message.feature` 包下,且在 `SFMessageDecorator` 的构造方法中被注册。
|
||||
类似 `.xyz`, `@xx` 的功能都位于 `message.feature` 包下,且在 `SimpleMessageDecorator` 的构造方法中被注册。
|
||||
它们实际上是 Minecraft 提供的 `MessageDecorator` 接口的实现。
|
||||
|
||||
通常,一条消息进入服务器后会轮流经过许多 decorator 的处理。因此为了避免 siblings 嵌套问题,`SimpleMessageDecorator` (也就是 `SFMessageDecorator` 的超类) 会自动将 decorator 返回的 Text 展开——
|
||||
通常,一条消息进入服务器后会轮流经过许多 decorator 的处理。因此为了避免 siblings 嵌套问题,`SimpleMessageDecorator` (也就是 `SimpleMessageDecorator` 的超类) 会自动将 decorator 返回的 Text 展开——
|
||||
具体的说,将返回的结果 copy,去除 siblings,然后将 siblings 逐个加入一个缓存队列中,下一个 Decorator 将会逐个处理队列中的 `Text`,且每次处理完后都会清理掉原本的缓存队列。因此,在没有特殊需求的情况下,请总是返回你 Decorator 不关注的信息。
|
||||
|
||||
一个简单的例子:
|
||||
|
||||
@@ -30,6 +30,7 @@ dependencies {
|
||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
shadow('com.maxmind.geoip2:geoip2:4.2.0')
|
||||
shadow('com.google.inject:guice:7.0.0')
|
||||
}
|
||||
configurations.implementation.extendsFrom(configurations.shadow)
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WhitelistEntry;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TextColor;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.StringHelper;
|
||||
import net.minecraft.util.Uuids;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class Commands {
|
||||
|
||||
public Commands() {
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(this::onDisconnect);
|
||||
}
|
||||
|
||||
private void onDisconnect(ServerPlayNetworkHandler serverPlayNetworkHandler, MinecraftServer minecraftServer) {
|
||||
serverPlayNetworkHandler.getPlayer().removeCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
}
|
||||
|
||||
public int addPlayerOffline(ServerCommandSource source, String player) {
|
||||
player = player.trim();
|
||||
var pm = source.getServer().getPlayerManager();
|
||||
var wl = pm.getWhitelist();
|
||||
if (List.of(wl.getNames()).contains(player)) {
|
||||
source.sendError(Text.of(player + " is already whitelisted or this id is conflict with a online user."));
|
||||
return 0;
|
||||
}
|
||||
var profile = Uuids.getOfflinePlayerProfile(player);
|
||||
wl.add(new WhitelistEntry(profile));
|
||||
source.sendMessage(Text.of("[" + profile.getName() + "/" + profile.getId() + "]" + " is added!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int onBack(CommandContext<ServerCommandSource> it) {
|
||||
var player = it.getSource().getPlayer();
|
||||
var pos = player.getLastDeathPos().get();
|
||||
var wld = player.server.getWorld(pos.dimension());
|
||||
var _pos = pos.pos();
|
||||
if (wld == null) return 0;
|
||||
var nearby = wld.getClosestPlayer(_pos.getX(), _pos.getY(), _pos.getZ(), 20, true);
|
||||
if (nearby != null) {
|
||||
_pos = nearby.getBlockPos();
|
||||
player.teleport(wld, _pos.getX(), _pos.getY(), _pos.getZ(), 0, 0);
|
||||
} else {
|
||||
if (SFConsts.UNLIMITED_COMMAND_BACK.hasPermission(player)) {
|
||||
player.teleport(wld, _pos.getX(), _pos.getY(), _pos.getZ(), 0, 0);
|
||||
return 0;
|
||||
}
|
||||
player.sendMessage(Text.of("周围没有玩家。").copy().withColor(Colors.RED));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int unblockServer(CommandContext<ServerCommandSource> ctx) {
|
||||
var state = SFCraft.getInstance().getConfig().disableTemporarily;
|
||||
SFCraft.getInstance().getConfig().disableTemporarily = !state;
|
||||
ctx.getSource().sendMessage(Text.of("Current state: " + !state));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int enterSudoMode(CommandContext<ServerCommandSource> serverCommandSourceCommandContext) {
|
||||
var player = serverCommandSourceCommandContext.getSource().getPlayer();
|
||||
if (player.getCommandTags().contains(SFConsts.SPECIAL_SUDO)) {
|
||||
player.removeCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
player.sendMessage(Text.of("Sudo is off.").copy().withColor(Colors.GREEN));
|
||||
} else {
|
||||
player.addCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
player.sendMessage(Text.of("Sudo is on.").copy().withColor(Colors.GREEN));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public int listPerms(CommandContext<ServerCommandSource> serverCommandSourceCommandContext) {
|
||||
var src = serverCommandSourceCommandContext.getSource();
|
||||
src.sendMessage(Text.of("Permissions: ").copy().withColor(Colors.GREEN));
|
||||
for (Field declaredField : SFConsts.class.getDeclaredFields()) {
|
||||
if (Permission.class.isAssignableFrom(declaredField.getType())) {
|
||||
var permission = (Permission<?>) declaredField.get(null);
|
||||
src.sendMessage(Text.of(" - " + permission.key() + " " + (permission.byDefault() ? "(default)" : "")));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int listOffline(CommandContext<ServerCommandSource> ctx) {
|
||||
var source = ctx.getSource();
|
||||
var server = source.getServer();
|
||||
var wl = server.getPlayerManager().getWhitelist();
|
||||
var names = wl.getNames();
|
||||
source.sendMessage(Text.of("Offline Users:").copy().withColor(Colors.GREEN));
|
||||
var r = Stream.of(names).filter(name -> wl.isAllowed(Uuids.getOfflinePlayerProfile(name))).collect(Collectors.joining(", "));
|
||||
source.sendMessage(Text.of(r.isEmpty() ? "No offline users found in whitelist." : r));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
public interface Lifecycle {
|
||||
void onInitialize();
|
||||
|
||||
void onStateChange(State state);
|
||||
|
||||
enum State {
|
||||
ENABLED, DISABLED,ERROR;
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import io.ib67.sfcraft.event.longnight.LongNightEvent;
|
||||
import lombok.SneakyThrows;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.component.type.WrittenBookContentComponent;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerMetadata;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.RawFilteredPair;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.util.Uuids;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Clock;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Listener {
|
||||
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
||||
private final SFCraft core;
|
||||
|
||||
public Listener(SFCraft core) {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public boolean onPlayerLogin(String currentPlayer, ClientConnection connection, Consumer<Text> disconnect) {
|
||||
var wl = core.getServer().getPlayerManager().getWhitelist();
|
||||
var offlineProfile = Uuids.getOfflinePlayerProfile(currentPlayer);
|
||||
if (connection.getAddress() instanceof InetSocketAddress address) {
|
||||
try {
|
||||
var clock = core.getGeoIPService().clockOf(address.getAddress());
|
||||
var config = core.getConfig();
|
||||
if (config.isClosed(clock) && !"iceBear67".equals(currentPlayer)) {
|
||||
final var msg = "服务器尚未开放\n服务器维护时间: %1$d~%2$d\n你的下午不值得浪费在游戏上,晚点再来吧!";
|
||||
disconnect.accept(Text.of(String.format(msg, config.maintainceStartHour, config.maintainceEndHour)));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
System.err.println("Failed to locate " + address.getAddress());
|
||||
}
|
||||
}
|
||||
return wl.isAllowed(offlineProfile);
|
||||
}
|
||||
|
||||
public void onPlayerDeath(ServerPlayerEntity player, DamageSource damageSource) {
|
||||
if (Helper.canBack(player)) {
|
||||
player.sendMessage(Text.of("Tip: 死亡地点附近有玩家,可以使用 /back 传送到他们那里。(即使在对方也死亡的状态下)"));
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayerJoin(ServerPlayNetworkHandler serverPlayNetworkHandler, PacketSender
|
||||
packetSender, MinecraftServer minecraftServer) {
|
||||
var player = serverPlayNetworkHandler.getPlayer();
|
||||
if (!player.getCommandTags().contains("has_joined_before")) {
|
||||
player.addCommandTag("has_joined_before");
|
||||
player.sendMessage(Text.of("欢迎加入 SaltedFish Club Server!"));
|
||||
player.sendMessage(Text.of("您受本服务器成员邀请并第一次加入本服务器,以下是一些注意事项:"));
|
||||
player.sendMessage(Text.of(" 1. 服务器内有 base,但我们也鼓励您自立门户"));
|
||||
player.sendMessage(Text.of(" 2. 下午 " + SFCraft.getInstance().getConfig().maintainceStartHour + " ~ " + SFCraft.getInstance().getConfig().maintainceEndHour + " 为服务器维护时间(防沉迷),在这段时间内无法加入服务器。"));
|
||||
player.sendMessage(Text.of("如果您想邀请朋友加入服务器,可以向管理员提出申请。本服务器为正版服,但是离线玩家也可以加入(需备注)。"));
|
||||
player.sendMessage(Text.of("为了账号安全起见,请尽量使用正版账号登录后加入。此外,正/盗同名将会被视为两个不同的玩家存档,数据不互通。"));
|
||||
player.giveItemStack(generateHelperBook(core.getUpdateLog(), player));
|
||||
unlockRecipe(player);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack generateHelperBook(String updateLog, ServerPlayerEntity player) {
|
||||
Clock clock = null;
|
||||
if (player.networkHandler.getConnectionAddress() instanceof InetSocketAddress addr) {
|
||||
try {
|
||||
clock = SFCraft.getInstance().getGeoIPService().clockOf(addr.getAddress());
|
||||
} catch (GeoIp2Exception e) {
|
||||
clock = Clock.systemDefaultZone();
|
||||
}
|
||||
}
|
||||
var book = new ItemStack(Items.WRITTEN_BOOK);
|
||||
var updLog = new ArrayList<String>();
|
||||
updLog.add("本书修订截至至您加入服务器的那一刻 (" + FMT.format(ZonedDateTime.now(clock)) + ")\n" + "更多内容请查阅群公告");
|
||||
var pages = updateLog.length() / 100;
|
||||
for (int i = 0; i < pages; i++) {
|
||||
updLog.add(updateLog.substring(i * 100, (i + 1) * 100));
|
||||
}
|
||||
updLog.add(updateLog.substring(pages * 100, updateLog.length()));
|
||||
var component = new WrittenBookContentComponent(
|
||||
RawFilteredPair.of("特性列表"),
|
||||
"icybear & You",
|
||||
1,
|
||||
updLog.stream().map(it -> RawFilteredPair.of(Text.of(it))).toList(),
|
||||
false
|
||||
);
|
||||
book.set(DataComponentTypes.WRITTEN_BOOK_CONTENT, component);
|
||||
return book;
|
||||
}
|
||||
|
||||
private void unlockRecipe(ServerPlayerEntity player) {
|
||||
var recipe = player.getRecipeBook();
|
||||
recipe.unlockRecipes(player.server.getRecipeManager().values(), player);
|
||||
}
|
||||
|
||||
private final Set<String> motdRequestedIps = new HashSet<>();
|
||||
|
||||
public ServerMetadata onMotdPing(MinecraftServer instance, ClientConnection connection) {
|
||||
var ip = connection.getAddressAsString(false);
|
||||
var cached = motdRequestedIps.contains(ip);
|
||||
if (!cached) {
|
||||
motdRequestedIps.add(ip);
|
||||
}
|
||||
var originalMetadata = instance.getServerMetadata();
|
||||
if (originalMetadata == null) return null;
|
||||
String version = null;
|
||||
if (connection.getAddress() instanceof InetSocketAddress address) {
|
||||
version = Helper.getVersionString(address);
|
||||
}
|
||||
if (version != null) {
|
||||
return new ServerMetadata(
|
||||
originalMetadata.description(),
|
||||
originalMetadata.players(),
|
||||
Optional.of(new ServerMetadata.Version(version, 0)),
|
||||
cached ? Optional.empty() : originalMetadata.favicon(),
|
||||
originalMetadata.secureChatEnforced()
|
||||
);
|
||||
} else {
|
||||
return originalMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
public Either<PlayerEntity.SleepFailureReason, Unit> onPlayerSleep(PlayerEntity player, BlockPos pos) {
|
||||
if (LongNightEvent.isRunning()) {
|
||||
player.sendMessage(Text.literal("你感到焦躁不安!").withColor(Colors.LIGHT_RED)
|
||||
.append(Text.literal(" (永夜事件期间无法睡觉)").withColor(Colors.LIGHT_GRAY)));
|
||||
return Either.left(PlayerEntity.SleepFailureReason.NOT_POSSIBLE_HERE);
|
||||
}
|
||||
return Either.right(Unit.INSTANCE);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,43 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.google.inject.*;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import io.ib67.sfcraft.message.SFMessageDecorator;
|
||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
||||
import io.ib67.sfcraft.event.SFRandomEventRegistry;
|
||||
import io.ib67.sfcraft.inject.ConfigRoot;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.network.message.MessageDecorator;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SFCraft implements ModInitializer {
|
||||
@Getter
|
||||
private SFConfig config;
|
||||
@Getter
|
||||
private Listener listener;
|
||||
@Getter
|
||||
private static SFCraft instance;
|
||||
@Getter
|
||||
private GeoIPService geoIPService;
|
||||
@Getter
|
||||
private Path root;
|
||||
@Getter
|
||||
private MinecraftServer server;
|
||||
@Getter
|
||||
private String motd;
|
||||
@Getter
|
||||
private String updateLog;
|
||||
@Getter
|
||||
private RandomEventRegistry randomEventRegistry;
|
||||
@Getter
|
||||
private MessageDecorator messageDecorator;
|
||||
public class SFCraft extends AbstractModule {
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
static Injector injector;
|
||||
/**
|
||||
* DO NOT USE THIS DIRECTLY
|
||||
* USE {@link io.ib67.sfcraft.inject.MinecraftServerSupplier} IN DI INSTEAD.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static MinecraftServer server;
|
||||
|
||||
@Override
|
||||
@Provides
|
||||
@ConfigRoot
|
||||
@Singleton
|
||||
public static Path getRoot() {
|
||||
return Path.of("sfcraft");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SneakyThrows
|
||||
public void onInitialize() {
|
||||
instance = this;
|
||||
root = Path.of("sfcraft");
|
||||
@Singleton
|
||||
@Inject
|
||||
private static SFConfig loadConfig(@ConfigRoot Path root, Gson gson) {
|
||||
if (Files.notExists(root)) {
|
||||
Files.createDirectory(root);
|
||||
}
|
||||
listener = new Listener(this);
|
||||
config = loadConfig();
|
||||
motd = loadMotd();
|
||||
updateLog = loadUpdateLog();
|
||||
geoIPService = new GeoIPService();
|
||||
|
||||
randomEventRegistry = setupRandomEvents();
|
||||
messageDecorator = setupMessageDecorator();
|
||||
|
||||
ServerPlayConnectionEvents.JOIN.register(listener::onPlayerJoin);
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommands);
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(this::onServerStarted);
|
||||
|
||||
}
|
||||
|
||||
private MessageDecorator setupMessageDecorator() {
|
||||
return new SFMessageDecorator();
|
||||
}
|
||||
|
||||
private SFRandomEventRegistry setupRandomEvents() {
|
||||
return new SFRandomEventRegistry();
|
||||
}
|
||||
|
||||
private void onServerStarted(MinecraftServer minecraftServer) {
|
||||
server = minecraftServer;
|
||||
server.setMotd(motd);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String loadUpdateLog() {
|
||||
var log = root.resolve("update.log");
|
||||
if (Files.notExists(log)) {
|
||||
return "他没写呢";
|
||||
}
|
||||
return Files.readString(log);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String loadMotd() {
|
||||
var motd = root.resolve("motd.txt");
|
||||
if (Files.notExists(motd)) {
|
||||
Files.writeString(motd, "§bSaltedFish Club Server");
|
||||
}
|
||||
return Files.readString(motd);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private SFConfig loadConfig() {
|
||||
var gson = new Gson();
|
||||
var cfgFile = root.resolve("config.json");
|
||||
if (Files.notExists(cfgFile)) {
|
||||
Files.writeString(cfgFile, gson.toJson(new SFConfig()));
|
||||
@@ -111,39 +45,4 @@ public class SFCraft implements ModInitializer {
|
||||
}
|
||||
return gson.fromJson(Files.readString(cfgFile), SFConfig.class);
|
||||
}
|
||||
|
||||
private void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) {
|
||||
var handler = new Commands();
|
||||
|
||||
// 一般非玩家实体都有 >= 2 的 PermissionLevel,因此不需要额外检查。
|
||||
dispatcher.register(
|
||||
LiteralArgumentBuilder.<ServerCommandSource>literal("addwl")
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_ADDWL.hasPermission(it.getPlayer()))
|
||||
.then(
|
||||
RequiredArgumentBuilder.<ServerCommandSource, String>argument("player", StringArgumentType.string())
|
||||
.executes(it -> handler.addPlayerOffline(it.getSource(), it.getArgument("player", String.class)))
|
||||
)
|
||||
|
||||
);
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("back")
|
||||
.requires(it -> it.getPlayer() != null)
|
||||
.requires(it -> it.getPlayer().getLastDeathPos().isPresent())
|
||||
.requires(it -> SFConsts.COMMAND_BACK.hasPermission(it.getPlayer()))
|
||||
.executes(handler::onBack));
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("unblockserver")
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_UNBLOCKSERVER.hasPermission(it.getPlayer()))
|
||||
.executes(handler::unblockServer));
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("sudo")
|
||||
.requires(it -> it.hasPermissionLevel(2) && it.getPlayer() != null)
|
||||
.executes(handler::enterSudoMode)
|
||||
);
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("listperm")
|
||||
.requires(it->it.hasPermissionLevel(2) || SFConsts.COMMAND_LISTPERM.hasPermission(it.getPlayer()))
|
||||
.executes(handler::listPerms)
|
||||
);
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("listoffline")
|
||||
.requires(it->it.hasPermissionLevel(2) || SFConsts.COMMAND_LISTOFFLINE.hasPermission(it.getPlayer()))
|
||||
.executes(handler::listOffline)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.inject.*;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import io.ib67.sfcraft.registry.event.SFRandomEventRegistry;
|
||||
import io.ib67.sfcraft.init.GuiceModInitializer;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import io.ib67.sfcraft.geoip.MaxMindGeoIPService;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.module.MotdModule;
|
||||
import io.ib67.sfcraft.module.OfflineExemptModule;
|
||||
import io.ib67.sfcraft.module.PlayLimitModule;
|
||||
import io.ib67.sfcraft.module.WelcomeModule;
|
||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
||||
import io.ib67.sfcraft.module.command.BackModule;
|
||||
import io.ib67.sfcraft.module.command.ManagementModule;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@Log4j2
|
||||
public class SFCraftInitializer extends GuiceModInitializer {
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
binder().bind(Gson.class).toInstance(new GsonBuilder().setPrettyPrinting().create());
|
||||
binder().bind(MinecraftServerSupplier.class).asEagerSingleton();
|
||||
registerServices();
|
||||
registerFeatures();
|
||||
}
|
||||
|
||||
private void registerServices() {
|
||||
binder().bind(GeoIPService.class).to(MaxMindGeoIPService.class);
|
||||
binder().bind(RandomEventRegistry.class).to(SFRandomEventRegistry.class);
|
||||
binder().bind(SimpleMessageDecorator.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
private void registerFeatures() {
|
||||
registerFeature(MotdModule.class);
|
||||
registerFeature(WelcomeModule.class);
|
||||
registerFeature(OfflineExemptModule.class);
|
||||
registerFeature(PlayLimitModule.class);
|
||||
registerFeature(BackModule.class);
|
||||
registerFeature(ManagementModule.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
protected void onReady(MinecraftServer minecraftServer) {
|
||||
log.info("SFCraft is loading");
|
||||
SFCraft.server = minecraftServer;
|
||||
SFCraft.injector = getInjector();
|
||||
getModuleManager().switchGlobalState(Lifecycle.State.ENABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Injector onInit() {
|
||||
return Guice.createInjector(
|
||||
this,
|
||||
new SFCraft()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ib67.sfcraft;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Getter
|
||||
@Log4j2
|
||||
public abstract class ServerModule extends AbstractModule implements Lifecycle {
|
||||
private State state;
|
||||
|
||||
@Override
|
||||
public final void onStateChange(State state) {
|
||||
try {
|
||||
switch (state) {
|
||||
case ENABLED -> onEnable();
|
||||
case DISABLED -> onDisable();
|
||||
}
|
||||
this.state = state;
|
||||
} catch (Exception exception) {
|
||||
log.error("An error occurred when " + getName() + " is changing its state into " + state);
|
||||
this.state = State.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
|
||||
}
|
||||
|
||||
public final boolean isEnabled() {
|
||||
return state == State.ENABLED;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.ib67.sfcraft.callback;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerMetadata;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static io.ib67.sfcraft.callback.Utility.*;
|
||||
|
||||
public interface SFCallbacks {
|
||||
|
||||
Event<PlayerPreLoginCallback> PRE_LOGIN = EventFactory.createArrayBacked(PlayerPreLoginCallback.class,
|
||||
(listeners) -> (s, c, d) -> anyMatch(listeners, i -> i.onPlayerPreLogin(s, c, d)));
|
||||
Event<PlayerSleepCallback> PLAYER_SLEEP = EventFactory.createArrayBacked(PlayerSleepCallback.class,
|
||||
l -> (pl, pos) -> anyMatch(l, i -> i.onPlayerSleep(pl, pos), a -> a.left().isPresent())
|
||||
.orElse(getEitherUnitR()));
|
||||
|
||||
Event<PlayerDeathCallback> PLAYER_DEATH = EventFactory.createArrayBacked(PlayerDeathCallback.class,
|
||||
l -> (pl, damageSource) -> forEach(l, i -> i.onPlayerDeath(pl, damageSource)));
|
||||
Event<ServerMotdCallback> MOTD = EventFactory.createArrayBacked(ServerMotdCallback.class,
|
||||
l -> (s, c) -> first(l, t -> t.onMotd(s, c)));
|
||||
|
||||
interface ServerMotdCallback {
|
||||
ServerMetadata onMotd(MinecraftServer server, ClientConnection connection);
|
||||
}
|
||||
|
||||
interface PlayerDeathCallback {
|
||||
void onPlayerDeath(PlayerEntity player, DamageSource damageSource);
|
||||
}
|
||||
|
||||
interface PlayerPreLoginCallback {
|
||||
/**
|
||||
* @return if let in
|
||||
*/
|
||||
boolean onPlayerPreLogin(String currentPlayer, ClientConnection connection, Consumer<Text> disconnect);
|
||||
}
|
||||
|
||||
interface PlayerSleepCallback {
|
||||
|
||||
Either<PlayerEntity.SleepFailureReason, Unit> onPlayerSleep(PlayerEntity player, BlockPos pos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ib67.sfcraft.callback;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.util.Unit;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class Utility {
|
||||
private static final Either<?, Unit> EITHER_UNIT_R = Either.right(Unit.INSTANCE);
|
||||
|
||||
static <T> Either<T, Unit> getEitherUnitR() {
|
||||
return (Either<T, Unit>) EITHER_UNIT_R;
|
||||
}
|
||||
|
||||
static <T, R> R first(T[] ts, Function<T, R> action) {
|
||||
if (ts.length == 0) return null;
|
||||
return action.apply(ts[0]);
|
||||
}
|
||||
|
||||
static <T> void forEach(T[] ts, Consumer<T> action) {
|
||||
for (T t : ts) {
|
||||
action.accept(t);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> boolean anyMatch(T[] ts, Predicate<T> predicate) {
|
||||
for (T handler : ts) {
|
||||
if (predicate.test(handler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static <T, A> Optional<A> anyMatch(T[] ts, Function<T, A> function, Predicate<A> failurePredicate) {
|
||||
A r = null;
|
||||
for (T handler : ts) {
|
||||
r = function.apply(handler);
|
||||
if (failurePredicate.test(r)) {
|
||||
return Optional.of(r);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.ib67.sfcraft.config;
|
||||
|
||||
public class ConfigResources {
|
||||
public static final String UPDATE_LOG="update.log";
|
||||
public static final String MOTD="motd.txt";
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class SFConfig {
|
||||
public boolean enableOfflineExempt = true;
|
||||
public int maintainceStartHour = 13;
|
||||
public int maintainceEndHour = 17;
|
||||
public boolean disableTemporarily;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package io.ib67.sfcraft.event;
|
||||
|
||||
import io.ib67.sfcraft.event.longnight.DawnAfterLongNightEvent;
|
||||
import io.ib67.sfcraft.event.longnight.LongNightEvent;
|
||||
import io.ib67.sfcraft.registry.event.SimpleRandomEventRegistry;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class SFRandomEventRegistry extends SimpleRandomEventRegistry {
|
||||
public SFRandomEventRegistry() {
|
||||
registerEvent(LongNightEvent::new, World.OVERWORLD
|
||||
, world -> !LongNightEvent.isRunning()
|
||||
&& world.getTimeOfDay() % 24000 == 18000
|
||||
&& world.getRandom().nextBetween(0,100) < 5); // At midnight
|
||||
registerEvent(DawnAfterLongNightEvent::new, World.OVERWORLD
|
||||
, world -> world.getTimeOfDay() % 22200 == 0); // At dawn
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,12 @@
|
||||
package io.ib67.sfcraft.geoip;
|
||||
|
||||
import com.maxmind.db.CHMCache;
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.maxmind.geoip2.record.Country;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneId;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static java.net.http.HttpResponse.BodyHandlers.ofFile;
|
||||
|
||||
@Log4j2
|
||||
public class GeoIPService implements Closeable {
|
||||
private static final URI GEOIP_DOWNLOAD_URL = URI.create("http://sfclub.cc/GeoLite2-City.mmdb");
|
||||
private static final Path COUNTRY_MMDB_PATH = SFCraft.getInstance().getRoot().resolve("GeoLite2-City.mmdb");
|
||||
private final DatabaseReader databaseReader;
|
||||
|
||||
public GeoIPService() throws InterruptedException {
|
||||
databaseReader = loadData();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Clock clockOf(InetAddress address) throws GeoIp2Exception {
|
||||
return Clock.system(TimeZone.getTimeZone(databaseReader.city(address).getLocation().getTimeZone()).toZoneId());
|
||||
}
|
||||
|
||||
private DatabaseReader loadData() throws InterruptedException {
|
||||
try {
|
||||
return new DatabaseReader.Builder(COUNTRY_MMDB_PATH.toFile()).withCache(new CHMCache()).build();
|
||||
} catch (IOException ignored) {
|
||||
return updateData();
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseReader updateData() throws InterruptedException {
|
||||
var client = HttpClient.newHttpClient();
|
||||
var req = HttpRequest.newBuilder(GEOIP_DOWNLOAD_URL).GET().build();
|
||||
int retries = 0;
|
||||
do {
|
||||
try {
|
||||
log.info("Downloading GeoIP database, please wait....");
|
||||
client.send(req, ofFile(COUNTRY_MMDB_PATH));
|
||||
return new DatabaseReader.Builder(COUNTRY_MMDB_PATH.toFile()).build();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("Failed to download or the database is corrupted!", e);
|
||||
int sleep = (int) Math.pow(2, ++retries);
|
||||
log.info("Retrying after " + sleep + "sec.");
|
||||
Thread.sleep(sleep);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
} while (retries < 4);
|
||||
throw new InterruptedException("Cannot load GeoIP data.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
databaseReader.close();
|
||||
}
|
||||
public interface GeoIPService extends Closeable {
|
||||
Clock clockOf(InetAddress address) throws GeoIp2Exception;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ib67.sfcraft.geoip;
|
||||
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.maxmind.db.CHMCache;
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.inject.ConfigRoot;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static java.net.http.HttpResponse.BodyHandlers.ofFile;
|
||||
|
||||
@Log4j2
|
||||
@Singleton
|
||||
public class MaxMindGeoIPService implements Module, GeoIPService {
|
||||
private static final URI GEOIP_DOWNLOAD_URL = URI.create("http://sfclub.cc/GeoLite2-City.mmdb");
|
||||
@Inject
|
||||
@ConfigRoot
|
||||
private Path configRoot;
|
||||
private Path mmdbPath;
|
||||
private final DatabaseReader databaseReader;
|
||||
|
||||
public MaxMindGeoIPService() throws InterruptedException {
|
||||
mmdbPath = configRoot.resolve("GeoLite2-City.mmdb");
|
||||
databaseReader = loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Clock clockOf(InetAddress address) throws GeoIp2Exception {
|
||||
return Clock.system(TimeZone.getTimeZone(databaseReader.city(address).getLocation().getTimeZone()).toZoneId());
|
||||
}
|
||||
|
||||
private DatabaseReader loadData() throws InterruptedException {
|
||||
try {
|
||||
return new DatabaseReader.Builder(mmdbPath.toFile()).withCache(new CHMCache()).build();
|
||||
} catch (IOException ignored) {
|
||||
return updateData();
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseReader updateData() throws InterruptedException {
|
||||
var client = HttpClient.newHttpClient();
|
||||
var req = HttpRequest.newBuilder(GEOIP_DOWNLOAD_URL).GET().build();
|
||||
int retries = 0;
|
||||
do {
|
||||
try {
|
||||
log.info("Downloading GeoIP database, please wait....");
|
||||
client.send(req, ofFile(mmdbPath));
|
||||
return new DatabaseReader.Builder(mmdbPath.toFile()).build();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("Failed to download or the database is corrupted!", e);
|
||||
int sleep = (int) Math.pow(2, ++retries);
|
||||
log.info("Retrying after " + sleep + "sec.");
|
||||
Thread.sleep(sleep);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
} while (retries < 4);
|
||||
throw new InterruptedException("Cannot load GeoIP data.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
databaseReader.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.ib67.sfcraft.init;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.module.manager.ModuleManager;
|
||||
import io.ib67.sfcraft.module.manager.SimpleModuleManager;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class GuiceModInitializer extends AbstractModule implements ModInitializer {
|
||||
public static AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private Injector injector;
|
||||
private ModuleManager moduleManager;
|
||||
private final List<Class<? extends ServerModule>> registeredFeatures = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
var handler = new PreInitHandler(initialized);
|
||||
ServerPlayConnectionEvents.JOIN.register(handler::defend);
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(this::onServerStarted);
|
||||
injector = onInit();
|
||||
}
|
||||
|
||||
private void onServerStarted(MinecraftServer server) {
|
||||
var map = new HashMap<String, ServerModule>();
|
||||
for (Class<? extends ServerModule> registeredFeature : registeredFeatures) {
|
||||
var module = injector.getInstance(registeredFeature);
|
||||
map.put(module.getName(), module);
|
||||
}
|
||||
moduleManager = new SimpleModuleManager(ImmutableMap.copyOf(map));
|
||||
onReady(server);
|
||||
initialized.set(true);
|
||||
}
|
||||
|
||||
protected abstract Injector onInit();
|
||||
|
||||
protected abstract void onReady(MinecraftServer minecraftServer);
|
||||
|
||||
@Provides
|
||||
protected ModuleManager getModuleManager() {
|
||||
return moduleManager;
|
||||
}
|
||||
|
||||
protected void registerFeature(Class<? extends ServerModule> featureClass) {
|
||||
binder().bind(featureClass).in(Singleton.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.ib67.sfcraft.init;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PreInitHandler {
|
||||
private final AtomicBoolean initialized;
|
||||
|
||||
void defend(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server) {
|
||||
if (!initialized.get()) {
|
||||
handler.disconnect(Text.of("Server is initializing, please wait."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ib67.sfcraft.inject;
|
||||
|
||||
import jakarta.inject.Qualifier;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ConfigResource {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ib67.sfcraft.inject;
|
||||
|
||||
import jakarta.inject.Qualifier;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Provides a {@link java.nio.file.Path} to config root (usually "./sfcraft")
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ConfigRoot {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ib67.sfcraft.inject;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class MinecraftServerSupplier implements Supplier<MinecraftServer> {
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public MinecraftServer get() {
|
||||
return SFCraft.server;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package io.ib67.sfcraft.message;
|
||||
|
||||
import io.ib67.sfcraft.message.feature.PingPlayerFeature;
|
||||
import io.ib67.sfcraft.message.feature.SendLocationFeature;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class SFMessageDecorator extends SimpleMessageDecorator {
|
||||
public SFMessageDecorator() {
|
||||
registerDecorator(new SendLocationFeature());
|
||||
registerDecorator(new PingPlayerFeature());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import io.ib67.sfcraft.Helper;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import net.minecraft.network.QueryableServer;
|
||||
import net.minecraft.network.handler.LegacyQueryHandler;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import io.ib67.sfcraft.module.randomevt.longnight.LongNightEvent;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public class MinecraftServerMixin {
|
||||
@ModifyConstant(method = "tickWorlds", constant = @Constant(intValue = 20))
|
||||
private int speedUpAtNight(int constant) {
|
||||
if (LongNightEvent.isRunning()) {
|
||||
return 5;
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
@@ -17,13 +17,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
public class PlayerEntityMixin {
|
||||
@Inject(method = "onDeath", at = @At("TAIL"))
|
||||
public void onDeath(DamageSource damageSource, CallbackInfo ci) {
|
||||
SFCraft.getInstance().getListener().onPlayerDeath((ServerPlayerEntity) (Object) this, damageSource);
|
||||
SFCallbacks.PLAYER_DEATH.invoker().onPlayerDeath((ServerPlayerEntity) (Object) this, damageSource);
|
||||
}
|
||||
|
||||
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
|
||||
public void onSleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> cir) {
|
||||
var result = SFCraft.getInstance().getListener().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
|
||||
if(result.left().isPresent()){
|
||||
var result = SFCallbacks.PLAYER_SLEEP.invoker().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
|
||||
if (result.left().isPresent()) {
|
||||
cir.setReturnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerMetadata;
|
||||
@@ -19,6 +20,6 @@ public abstract class ServerHandshakeNetworkHandlerMixin {
|
||||
|
||||
@Redirect(method = "onHandshake", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getServerMetadata()Lnet/minecraft/server/ServerMetadata;"))
|
||||
public ServerMetadata getMetadata(MinecraftServer instance) {
|
||||
return SFCraft.getInstance().getListener().onMotdPing(instance,connection);
|
||||
return SFCallbacks.MOTD.invoker().onMotd(instance,connection);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,35 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerLoginNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Uuids;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
@Mixin(ServerLoginNetworkHandler.class)
|
||||
public abstract class ServerLoginNetworkHandlerMixin {
|
||||
@Shadow
|
||||
public abstract void disconnect(Text reason);
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
MinecraftServer server;
|
||||
@Unique
|
||||
private String currentPlayer;
|
||||
|
||||
@Inject(method = "onHello", at = @At("HEAD"))
|
||||
@@ -26,7 +39,24 @@ public abstract class ServerLoginNetworkHandlerMixin {
|
||||
|
||||
@Redirect(method = "onHello", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;isLocal()Z"))
|
||||
public boolean isLocal(ClientConnection connection) {
|
||||
return SFCraft.getInstance().getListener().onPlayerLogin(currentPlayer, connection, this::disconnect);
|
||||
if (!(connection.getAddress() instanceof InetSocketAddress)) {
|
||||
return connection.isLocal(); // 碰上真 isLocal 了
|
||||
}
|
||||
var offlineProfile = Uuids.getOfflinePlayerProfile(currentPlayer);
|
||||
boolean _r;
|
||||
if (server.getUserCache() == null) {
|
||||
// falling back to whitelist
|
||||
var wl = server.getPlayerManager().getWhitelist();
|
||||
_r = wl.isAllowed(offlineProfile);
|
||||
} else {
|
||||
var profile = server.getUserCache().getByUuid(offlineProfile.getId());
|
||||
_r = profile.isPresent();
|
||||
}
|
||||
var result = SFCallbacks.PRE_LOGIN.invoker().onPlayerPreLogin(currentPlayer, connection, this::disconnect);
|
||||
if (result) {
|
||||
return _r; // (is offline -> !true -> startVerify)
|
||||
}
|
||||
return !_r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
package io.ib67.sfcraft.mixin.server;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import net.minecraft.network.message.MessageChainTaskQueue;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import net.minecraft.network.message.MessageDecorator;
|
||||
import net.minecraft.network.message.SignedMessage;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(ServerPlayNetworkHandler.class)
|
||||
public abstract class ServerPlayNetworkHandlerMixin {
|
||||
/**
|
||||
* @author icybear
|
||||
* @reason small method, provide custom one
|
||||
* @reason small method, overwrite to reduce overhead
|
||||
*/
|
||||
@Redirect(method = "method_44900", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getMessageDecorator()Lnet/minecraft/network/message/MessageDecorator;"))
|
||||
public MessageDecorator getMessageDecorator(MinecraftServer instance) {
|
||||
return SFCraft.getInstance().getMessageDecorator();
|
||||
return SFCraft.getInjector().getInstance(SimpleMessageDecorator.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ib67.sfcraft.module;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.config.ConfigResources;
|
||||
import io.ib67.sfcraft.inject.ConfigResource;
|
||||
import io.ib67.sfcraft.inject.ConfigRoot;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class MotdModule extends ServerModule {
|
||||
@Inject
|
||||
@ConfigRoot
|
||||
private Path configRoot;
|
||||
@Inject
|
||||
private MinecraftServerSupplier minecraftServer;
|
||||
protected String motd;
|
||||
protected String originalMotd;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
motd = getMotd(configRoot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
originalMotd = minecraftServer.get().getServerMotd();
|
||||
minecraftServer.get().setMotd(motd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
minecraftServer.get().setMotd(originalMotd);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ConfigResource(ConfigResources.MOTD)
|
||||
@Inject
|
||||
@Singleton
|
||||
private static String getMotd(@ConfigRoot Path root) {
|
||||
return Helper.getConfigResource(root, ConfigResources.MOTD).orElse("SaltedFish Club Server");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package io.ib67.sfcraft.module;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.WhitelistEntry;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.Uuids;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class OfflineExemptModule extends ServerModule {
|
||||
@Inject
|
||||
private SFConfig config;
|
||||
private boolean state;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
state = config.enableOfflineExempt;
|
||||
SFCallbacks.PRE_LOGIN.register(this::onPreLogin);
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommand);
|
||||
}
|
||||
|
||||
private void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) {
|
||||
dispatcher.register(
|
||||
LiteralArgumentBuilder.<ServerCommandSource>literal("addwl")
|
||||
.requires(i -> isEnabled())
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_ADDWL.hasPermission(it.getPlayer()))
|
||||
.then(RequiredArgumentBuilder.<ServerCommandSource, String>argument("player", StringArgumentType.string())
|
||||
.executes(it -> this.addPlayerOffline(it.getSource(), it.getArgument("player", String.class))))
|
||||
|
||||
);
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("listoffline")
|
||||
.requires(i -> isEnabled())
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_LISTOFFLINE.hasPermission(it.getPlayer()))
|
||||
.executes(this::listOffline)
|
||||
);
|
||||
}
|
||||
|
||||
public int addPlayerOffline(ServerCommandSource source, String player) {
|
||||
player = player.trim();
|
||||
var pm = source.getServer().getPlayerManager();
|
||||
var wl = pm.getWhitelist();
|
||||
if (List.of(wl.getNames()).contains(player)) {
|
||||
source.sendError(Text.of(player + " is already whitelisted or this id is conflict with a online user."));
|
||||
return 0;
|
||||
}
|
||||
var profile = Uuids.getOfflinePlayerProfile(player);
|
||||
wl.add(new WhitelistEntry(profile));
|
||||
source.sendMessage(Text.of("[" + profile.getName() + "/" + profile.getId() + "]" + " is added!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int listOffline(CommandContext<ServerCommandSource> ctx) {
|
||||
var source = ctx.getSource();
|
||||
var server = source.getServer();
|
||||
var wl = server.getPlayerManager().getWhitelist();
|
||||
var names = wl.getNames();
|
||||
source.sendMessage(Text.of("Offline Users:").copy().withColor(Colors.GREEN));
|
||||
var r = Stream.of(names).filter(name -> wl.isAllowed(Uuids.getOfflinePlayerProfile(name))).collect(Collectors.joining(", "));
|
||||
source.sendMessage(Text.of(r.isEmpty() ? "No offline users found in whitelist." : r));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
state = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
state = true;
|
||||
}
|
||||
|
||||
private boolean onPreLogin(String s, ClientConnection connection, Consumer<Text> textConsumer) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ib67.sfcraft.module;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerMetadata;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PlayLimitModule extends ServerModule {
|
||||
private final Set<String> motdRequestedIps = new HashSet<>();
|
||||
@Inject
|
||||
GeoIPService geoIPService;
|
||||
@Inject
|
||||
SFConfig config;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
SFCallbacks.PRE_LOGIN.register(this::onPreLogin);
|
||||
SFCallbacks.MOTD.register(this::onMotd);
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommand);
|
||||
}
|
||||
|
||||
private void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) {
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("unblockserver")
|
||||
.requires(i -> isEnabled())
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_UNBLOCKSERVER.hasPermission(it.getPlayer()))
|
||||
.executes(this::unblockServer));
|
||||
}
|
||||
|
||||
public int unblockServer(CommandContext<ServerCommandSource> ctx) {
|
||||
var state = config.disableTemporarily;
|
||||
config.disableTemporarily = !state;
|
||||
ctx.getSource().sendMessage(Text.of("Current state: " + !state));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ServerMetadata onMotd(MinecraftServer server, ClientConnection connection) {
|
||||
var ip = connection.getAddressAsString(false);
|
||||
var cached = motdRequestedIps.contains(ip);
|
||||
if (!cached) {
|
||||
motdRequestedIps.add(ip);
|
||||
}
|
||||
var originalMetadata = server.getServerMetadata();
|
||||
if (originalMetadata == null) return null;
|
||||
String version = null;
|
||||
if (connection.getAddress() instanceof InetSocketAddress address) {
|
||||
version = Helper.getVersionString(address);
|
||||
}
|
||||
if (version != null && isEnabled()) {
|
||||
return new ServerMetadata(
|
||||
originalMetadata.description(),
|
||||
originalMetadata.players(),
|
||||
Optional.of(new ServerMetadata.Version(version, 0)),
|
||||
cached ? Optional.empty() : originalMetadata.favicon(),
|
||||
originalMetadata.secureChatEnforced()
|
||||
);
|
||||
} else {
|
||||
return originalMetadata;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean onPreLogin(String currentPlayer, ClientConnection connection, Consumer<Text> disconnect) {
|
||||
if (isEnabled() && connection.getAddress() instanceof InetSocketAddress address) {
|
||||
try {
|
||||
var clock = geoIPService.clockOf(address.getAddress());
|
||||
if (config.isClosed(clock) && !"iceBear67".equals(currentPlayer)) {
|
||||
final var msg = "服务器尚未开放\n服务器维护时间: %1$d~%2$d\n你的下午不值得浪费在游戏上,晚点再来吧!";
|
||||
disconnect.accept(Text.of(String.format(msg, config.maintainceStartHour, config.maintainceEndHour)));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
System.err.println("Failed to locate " + address.getAddress());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package io.ib67.sfcraft.module;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provides;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.config.ConfigResources;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import io.ib67.sfcraft.inject.ConfigResource;
|
||||
import io.ib67.sfcraft.inject.ConfigRoot;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.component.type.WrittenBookContentComponent;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.RawFilteredPair;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static io.ib67.sfcraft.util.Helper.getConfigResource;
|
||||
|
||||
public class WelcomeModule extends ServerModule {
|
||||
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
||||
@Inject
|
||||
private SFConfig config;
|
||||
@Inject
|
||||
@ConfigRoot
|
||||
private Path root;
|
||||
@Inject
|
||||
private GeoIPService geoIPService;
|
||||
private String updateLog;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
updateLog = getConfigResource(root, ConfigResources.UPDATE_LOG).orElse("他没写呢");
|
||||
ServerPlayConnectionEvents.JOIN.register(this::onPlayerJoin);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ConfigResource(ConfigResources.UPDATE_LOG)
|
||||
private String getUpdateLog() {
|
||||
return updateLog;
|
||||
}
|
||||
|
||||
public void onPlayerJoin(ServerPlayNetworkHandler serverPlayNetworkHandler, PacketSender
|
||||
packetSender, MinecraftServer minecraftServer) {
|
||||
var player = serverPlayNetworkHandler.getPlayer();
|
||||
if (!player.getCommandTags().contains("has_joined_before") && isEnabled()) {
|
||||
player.addCommandTag("has_joined_before");
|
||||
player.sendMessage(Text.of("欢迎加入 SaltedFish Club Server!"));
|
||||
player.sendMessage(Text.of("您受本服务器成员邀请并第一次加入本服务器,以下是一些注意事项:"));
|
||||
player.sendMessage(Text.of(" 1. 服务器内有 base,但我们也鼓励您自立门户"));
|
||||
player.sendMessage(Text.of(" 2. 下午 " + config.maintainceStartHour + " ~ " + config.maintainceEndHour + " 为服务器维护时间(防沉迷),在这段时间内无法加入服务器。"));
|
||||
player.sendMessage(Text.of("如果您想邀请朋友加入服务器,可以向管理员提出申请。本服务器为正版服,但是离线玩家也可以加入(需备注)。"));
|
||||
player.sendMessage(Text.of("为了账号安全起见,请尽量使用正版账号登录后加入。此外,正/盗同名将会被视为两个不同的玩家存档,数据不互通。"));
|
||||
player.giveItemStack(generateHelperBook(updateLog, player));
|
||||
unlockRecipe(player);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack generateHelperBook(String updateLog, ServerPlayerEntity player) {
|
||||
Clock clock = null;
|
||||
if (player.networkHandler.getConnectionAddress() instanceof InetSocketAddress addr) {
|
||||
try {
|
||||
clock = geoIPService.clockOf(addr.getAddress());
|
||||
} catch (GeoIp2Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (clock == null) clock = Clock.systemDefaultZone();
|
||||
var book = new ItemStack(Items.WRITTEN_BOOK);
|
||||
var updLog = new ArrayList<String>();
|
||||
updLog.add("本书修订截至至您加入服务器的那一刻 (" + FMT.format(ZonedDateTime.now(clock)) + ")\n" + "更多内容请查阅群公告");
|
||||
var pages = updateLog.length() / 100;
|
||||
for (int i = 0; i < pages; i++) {
|
||||
updLog.add(updateLog.substring(i * 100, (i + 1) * 100));
|
||||
}
|
||||
updLog.add(updateLog.substring(pages * 100));
|
||||
var component = new WrittenBookContentComponent(
|
||||
RawFilteredPair.of("特性列表"),
|
||||
"icybear & You",
|
||||
1,
|
||||
updLog.stream().map(it -> RawFilteredPair.of(Text.of(it))).toList(),
|
||||
false
|
||||
);
|
||||
book.set(DataComponentTypes.WRITTEN_BOOK_CONTENT, component);
|
||||
return book;
|
||||
}
|
||||
|
||||
private void unlockRecipe(ServerPlayerEntity player) {
|
||||
var recipe = player.getRecipeBook();
|
||||
recipe.unlockRecipes(player.server.getRecipeManager().values(), player);
|
||||
}
|
||||
}
|
||||
+21
-8
@@ -1,10 +1,12 @@
|
||||
package io.ib67.sfcraft.message.feature;
|
||||
package io.ib67.sfcraft.module.chat;
|
||||
|
||||
import io.ib67.sfcraft.SFConsts;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.network.message.MessageDecorator;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
@@ -13,18 +15,29 @@ import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class PingPlayerFeature implements MessageDecorator {
|
||||
public class ChatPingModule extends ServerModule implements MessageDecorator {
|
||||
private static final Pattern PING = Pattern.compile("(@[\\w]+)?");
|
||||
@Inject
|
||||
MinecraftServerSupplier serverSupplier;
|
||||
@Inject
|
||||
SimpleMessageDecorator messageDecorator;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
messageDecorator.registerDecorator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
||||
if (!isEnabled()) {
|
||||
return message;
|
||||
}
|
||||
if (sender != null) {
|
||||
if (!SFConsts.USE_AT.hasPermission(sender)) {
|
||||
return message;
|
||||
@@ -35,7 +48,7 @@ public class PingPlayerFeature implements MessageDecorator {
|
||||
var match = PING.matcher(text);
|
||||
if (!match.find()) return message;
|
||||
var foundPlayers = new HashSet<PlayerEntity>();
|
||||
var names = List.of(SFCraft.getInstance().getServer().getPlayerNames());
|
||||
var names = List.of(serverSupplier.get().getPlayerNames());
|
||||
var r = Text.literal(match.replaceAll(it -> this.matchPlayer(it, names, foundPlayers)));
|
||||
for (PlayerEntity foundPlayer : foundPlayers) {
|
||||
if (sender != null) {
|
||||
@@ -58,7 +71,7 @@ public class PingPlayerFeature implements MessageDecorator {
|
||||
final var d = r.substring(1);
|
||||
return playerNames.stream()
|
||||
.filter(it -> it.toLowerCase().startsWith(d))
|
||||
.peek(it -> foundPlayers.add(SFCraft.getInstance().getServer().getPlayerManager().getPlayer(it)))
|
||||
.peek(it -> foundPlayers.add(serverSupplier.get().getPlayerManager().getPlayer(it)))
|
||||
.findFirst()
|
||||
.orElse(r);
|
||||
}
|
||||
+18
-6
@@ -1,7 +1,10 @@
|
||||
package io.ib67.sfcraft.message.feature;
|
||||
package io.ib67.sfcraft.module.chat;
|
||||
|
||||
import io.ib67.sfcraft.Helper;
|
||||
import io.ib67.sfcraft.SFConsts;
|
||||
import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import net.minecraft.network.message.MessageDecorator;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
@@ -10,15 +13,24 @@ import net.minecraft.text.Text;
|
||||
import net.minecraft.world.World;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SendLocationFeature implements MessageDecorator {
|
||||
public class ChatSendLocModule extends ServerModule implements MessageDecorator {
|
||||
@Inject
|
||||
SimpleMessageDecorator messageDecorator;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
messageDecorator.registerDecorator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
||||
if (!isEnabled()) return message;
|
||||
if (sender != null) {
|
||||
if (!SFConsts.USE_BROADCAST_LOCATION.hasPermission(sender)) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
return ReplaceHelper.replace(sender, message, ".xyz", SendLocationFeature::generateLocText);
|
||||
return ReplaceHelper.replace(sender, message, ".xyz", ChatSendLocModule::generateLocText);
|
||||
}
|
||||
|
||||
private static Text generateLocText(ServerPlayerEntity sender) {
|
||||
@@ -45,4 +57,4 @@ public class SendLocationFeature implements MessageDecorator {
|
||||
return " (未知)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ib67.sfcraft.message.feature;
|
||||
package io.ib67.sfcraft.module.chat;
|
||||
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.ib67.sfcraft.module.command;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
|
||||
public class BackModule extends ServerModule {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
SFCallbacks.PLAYER_DEATH.register(this::onPlayerDeath);
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommands);
|
||||
}
|
||||
|
||||
private void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) {
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("back")
|
||||
.requires(it -> this.isEnabled())
|
||||
.requires(it -> it.getPlayer() != null)
|
||||
.requires(it -> it.getPlayer().getLastDeathPos().isPresent())
|
||||
.requires(it -> SFConsts.COMMAND_BACK.hasPermission(it.getPlayer()))
|
||||
.executes(this::onBack));
|
||||
}
|
||||
|
||||
public int onBack(CommandContext<ServerCommandSource> it) {
|
||||
var player = it.getSource().getPlayer();
|
||||
var pos = player.getLastDeathPos().get();
|
||||
var wld = player.server.getWorld(pos.dimension());
|
||||
var _pos = pos.pos();
|
||||
if (wld == null) return 0;
|
||||
var nearby = wld.getClosestPlayer(_pos.getX(), _pos.getY(), _pos.getZ(), 20, true);
|
||||
if (nearby != null) {
|
||||
_pos = nearby.getBlockPos();
|
||||
player.teleport(wld, _pos.getX(), _pos.getY(), _pos.getZ(), 0, 0);
|
||||
} else {
|
||||
if (SFConsts.UNLIMITED_COMMAND_BACK.hasPermission(player)) {
|
||||
player.teleport(wld, _pos.getX(), _pos.getY(), _pos.getZ(), 0, 0);
|
||||
return 0;
|
||||
}
|
||||
player.sendMessage(Text.of("周围没有玩家。").copy().withColor(Colors.RED));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onPlayerDeath(PlayerEntity player, DamageSource damageSource) {
|
||||
if (this.isEnabled() && Helper.canBack((ServerPlayerEntity) player)) {
|
||||
player.sendMessage(Text.of("Tip: 死亡地点附近有玩家,可以使用 /back 传送到他们那里。(即使在对方也死亡的状态下)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.ib67.sfcraft.module.command;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.util.Permission;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import lombok.SneakyThrows;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ManagementModule extends ServerModule {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommands);
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(this::onDisconnect);
|
||||
}
|
||||
|
||||
private void onDisconnect(ServerPlayNetworkHandler serverPlayNetworkHandler, MinecraftServer minecraftServer) {
|
||||
serverPlayNetworkHandler.getPlayer().removeCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
}
|
||||
|
||||
private void registerCommands(
|
||||
CommandDispatcher<ServerCommandSource> dispatcher,
|
||||
CommandRegistryAccess registryAccess,
|
||||
CommandManager.RegistrationEnvironment env) {
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("sudo")
|
||||
.requires(it -> this.isEnabled())
|
||||
.requires(it -> it.hasPermissionLevel(2) && it.getPlayer() != null)
|
||||
.executes(this::enterSudoMode)
|
||||
);
|
||||
dispatcher.register(LiteralArgumentBuilder.<ServerCommandSource>literal("listperm")
|
||||
.requires(it -> this.isEnabled())
|
||||
.requires(it -> it.hasPermissionLevel(2) || SFConsts.COMMAND_LISTPERM.hasPermission(it.getPlayer()))
|
||||
.executes(this::listPerms)
|
||||
);
|
||||
}
|
||||
|
||||
public int enterSudoMode(CommandContext<ServerCommandSource> ctx) {
|
||||
var player = ctx.getSource().getPlayer();
|
||||
if (player.getCommandTags().contains(SFConsts.SPECIAL_SUDO)) {
|
||||
player.removeCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
player.sendMessage(Text.of("Sudo is off.").copy().withColor(Colors.GREEN));
|
||||
} else {
|
||||
player.addCommandTag(SFConsts.SPECIAL_SUDO);
|
||||
player.sendMessage(Text.of("Sudo is on.").copy().withColor(Colors.GREEN));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public int listPerms(CommandContext<ServerCommandSource> ctx) {
|
||||
var src = ctx.getSource();
|
||||
src.sendMessage(Text.of("Permissions: ").copy().withColor(Colors.GREEN));
|
||||
for (Field declaredField : SFConsts.class.getDeclaredFields()) {
|
||||
if (Permission.class.isAssignableFrom(declaredField.getType())) {
|
||||
var permission = (Permission<?>) declaredField.get(null);
|
||||
src.sendMessage(Text.of(" - " + permission.key() + " " + (permission.byDefault() ? "(default)" : "")));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ib67.sfcraft.module.manager;
|
||||
|
||||
import io.ib67.sfcraft.Lifecycle;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ModuleManager {
|
||||
Collection<? extends ServerModule> getModules();
|
||||
|
||||
Optional<? extends ServerModule> getModule(String name);
|
||||
|
||||
Lifecycle.State getCurrentState();
|
||||
|
||||
/**
|
||||
* @param state nextState, cannot be current state.
|
||||
* @return error features
|
||||
*/
|
||||
List<? extends ServerModule> switchGlobalState(Lifecycle.State state);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.ib67.sfcraft.module.manager;
|
||||
|
||||
import io.ib67.sfcraft.Lifecycle;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleModuleManager implements ModuleManager {
|
||||
private final Map<String, ServerModule> modules;
|
||||
@Getter
|
||||
private Lifecycle.State currentState;
|
||||
|
||||
@Override
|
||||
public List<? extends ServerModule> switchGlobalState(Lifecycle.State state) {
|
||||
requireNonNull(state, "state cannot be null");
|
||||
if (currentState == state) {
|
||||
throw new IllegalArgumentException("currentState == targetState == " + state);
|
||||
}
|
||||
if (state == Lifecycle.State.ERROR) {
|
||||
throw new IllegalArgumentException("targetState == " + state);
|
||||
}
|
||||
var result = new ArrayList<ServerModule>();
|
||||
for (ServerModule value : modules.values()) {
|
||||
value.onStateChange(state);
|
||||
if (value.getState() == Lifecycle.State.ERROR) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
currentState = state;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends ServerModule> getModules() {
|
||||
return new ArrayList<>(modules.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<? extends ServerModule> getModule(String name) {
|
||||
requireNonNull(name, "name cannot be null");
|
||||
return Optional.ofNullable(modules.get(name));
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
package io.ib67.sfcraft.registry.event;
|
||||
|
||||
import io.ib67.sfcraft.event.RandomEvent;
|
||||
package io.ib67.sfcraft.module.randomevt;
|
||||
|
||||
public interface ActiveEvent {
|
||||
RandomEvent getEvent();
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ib67.sfcraft.module.randomevt;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import io.ib67.sfcraft.module.randomevt.longnight.DawnAfterLongNightEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.longnight.LongNightEvent;
|
||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class LongNightModule extends ServerModule {
|
||||
@Inject
|
||||
RandomEventRegistry registry;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
registry.registerEvent(LongNightEvent::new, World.OVERWORLD
|
||||
, world -> !LongNightEvent.isRunning()
|
||||
&& isEnabled()
|
||||
&& world.getTimeOfDay() % 24000 == 18000
|
||||
&& world.getRandom().nextBetween(0, 100) < 5); // At midnight
|
||||
registry.registerEvent(DawnAfterLongNightEvent::new, World.OVERWORLD
|
||||
, world -> world.getTimeOfDay() % 22200 == 0); // At dawn
|
||||
SFCallbacks.PLAYER_SLEEP.register(this::onPlayerSleep);
|
||||
}
|
||||
|
||||
public Either<PlayerEntity.SleepFailureReason, Unit> onPlayerSleep(PlayerEntity player, BlockPos pos) {
|
||||
if (!isEnabled()) return Either.right(Unit.INSTANCE);
|
||||
if (LongNightEvent.isRunning()) {
|
||||
player.sendMessage(Text.literal("你感到焦躁不安!").withColor(Colors.LIGHT_RED)
|
||||
.append(Text.literal(" (永夜事件期间无法睡觉)").withColor(Colors.LIGHT_GRAY)));
|
||||
return Either.left(PlayerEntity.SleepFailureReason.NOT_POSSIBLE_HERE);
|
||||
}
|
||||
return Either.right(Unit.INSTANCE);
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
package io.ib67.sfcraft.event;
|
||||
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.world.World;
|
||||
package io.ib67.sfcraft.module.randomevt;
|
||||
|
||||
public abstract class RandomEvent {
|
||||
/**
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package io.ib67.sfcraft.event.longnight;
|
||||
package io.ib67.sfcraft.module.randomevt.longnight;
|
||||
|
||||
import io.ib67.sfcraft.event.RandomEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package io.ib67.sfcraft.event.longnight;
|
||||
package io.ib67.sfcraft.module.randomevt.longnight;
|
||||
|
||||
import io.ib67.sfcraft.event.RandomEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package io.ib67.sfcraft.message;
|
||||
package io.ib67.sfcraft.registry.chat;
|
||||
|
||||
import net.minecraft.network.message.MessageDecorator;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
@@ -15,7 +15,7 @@ public class SimpleMessageDecorator implements MessageDecorator {
|
||||
|
||||
}
|
||||
|
||||
protected void registerDecorator(MessageDecorator decorator) {
|
||||
public void registerDecorator(MessageDecorator decorator) {
|
||||
decorators.add(decorator);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ib67.sfcraft.registry.event;
|
||||
|
||||
import io.ib67.sfcraft.event.RandomEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.ActiveEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.ib67.sfcraft.registry.event;
|
||||
|
||||
public class SFRandomEventRegistry extends SimpleRandomEventRegistry {
|
||||
public SFRandomEventRegistry() {
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ib67.sfcraft.registry.event;
|
||||
|
||||
import io.ib67.sfcraft.event.RandomEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.ActiveEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
|
||||
+18
-3
@@ -1,9 +1,16 @@
|
||||
package io.ib67.sfcraft;
|
||||
package io.ib67.sfcraft.util;
|
||||
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Helper {
|
||||
public static boolean canBack(ServerPlayerEntity player) {
|
||||
@@ -17,9 +24,10 @@ public class Helper {
|
||||
}
|
||||
|
||||
public static String getVersionString(InetSocketAddress address) {
|
||||
var geoIp = SFCraft.getInjector().getInstance(GeoIPService.class);
|
||||
var config = SFCraft.getInjector().getInstance(SFConfig.class);
|
||||
try {
|
||||
var clock = SFCraft.getInstance().getGeoIPService().clockOf(address.getAddress());
|
||||
var config = SFCraft.getInstance().getConfig();
|
||||
var clock = geoIp.clockOf(address.getAddress());
|
||||
if (config.isClosed(clock)) {
|
||||
return "防沉迷: " + config.maintainceStartHour + "~" + config.maintainceEndHour;
|
||||
}
|
||||
@@ -28,6 +36,13 @@ public class Helper {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@SneakyThrows
|
||||
public static Optional<String> getConfigResource(Path root, String resource) {
|
||||
if (Files.exists(root)) {
|
||||
return Optional.of(Files.readString(root.resolve(resource)));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static int fromRgb(int r, int g, int b) {
|
||||
return ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package io.ib67.sfcraft;
|
||||
package io.ib67.sfcraft.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.minecraft.entity.Entity;
|
||||
|
||||
import java.util.Objects;
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package io.ib67.sfcraft;
|
||||
package io.ib67.sfcraft.util;
|
||||
|
||||
import io.ib67.sfcraft.util.Permission;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
@UtilityClass
|
||||
public class SFConsts {
|
||||
@@ -13,7 +13,7 @@
|
||||
"environment": "server",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"io.ib67.sfcraft.SFCraft"
|
||||
"io.ib67.sfcraft.SFCraftInitializer"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
|
||||
Reference in new issue
Block a user