refactor WIP
This commit is contained in:
51 files changed
+1188
-543
No files matched your search
+3
-3
@@ -29,7 +29,7 @@ SFCraft 是一个 Fabric Mod,他的所有内容都是放在一起的,下列
|
|||||||
在此类定义的字段将会自动作为配置文件的一部分生成。你可以通过调用 `SFCraft.getInstance().getConfig()` 得到配置对象。
|
在此类定义的字段将会自动作为配置文件的一部分生成。你可以通过调用 `SFCraft.getInstance().getConfig()` 得到配置对象。
|
||||||
- `io.ib67.sfcraft.Listener` 是事件监听器。大多数回调/事件的处理代码放在这里。
|
- `io.ib67.sfcraft.Listener` 是事件监听器。大多数回调/事件的处理代码放在这里。
|
||||||
- `io.ib67.sfcraft.Commands` 是处理命令逻辑的地方,而命令在 `SFCraft#registerCommands` 方法注册。
|
- `io.ib67.sfcraft.Commands` 是处理命令逻辑的地方,而命令在 `SFCraft#registerCommands` 方法注册。
|
||||||
- `io.ib67.sfcraft.SFConsts` 存放一些频繁使用的常量,例如权限。
|
- `io.ib67.sfcraft.util.SFConsts` 存放一些频繁使用的常量,例如权限。
|
||||||
- `io.ib67.sfcraft.mixin.server` 下是一些 `Mixin` 类,接下来会讲。
|
- `io.ib67.sfcraft.mixin.server` 下是一些 `Mixin` 类,接下来会讲。
|
||||||
|
|
||||||
最后,`io.ib67.sfcraft.SFCraft` 是整个 Mod 的入口,也是核心部分。你可以从 `SFCraft.getInstance()` 返回的对象得到 Mod 里的大多数组件。
|
最后,`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` 接口的实现。
|
它们实际上是 Minecraft 提供的 `MessageDecorator` 接口的实现。
|
||||||
|
|
||||||
通常,一条消息进入服务器后会轮流经过许多 decorator 的处理。因此为了避免 siblings 嵌套问题,`SimpleMessageDecorator` (也就是 `SFMessageDecorator` 的超类) 会自动将 decorator 返回的 Text 展开——
|
通常,一条消息进入服务器后会轮流经过许多 decorator 的处理。因此为了避免 siblings 嵌套问题,`SimpleMessageDecorator` (也就是 `SimpleMessageDecorator` 的超类) 会自动将 decorator 返回的 Text 展开——
|
||||||
具体的说,将返回的结果 copy,去除 siblings,然后将 siblings 逐个加入一个缓存队列中,下一个 Decorator 将会逐个处理队列中的 `Text`,且每次处理完后都会清理掉原本的缓存队列。因此,在没有特殊需求的情况下,请总是返回你 Decorator 不关注的信息。
|
具体的说,将返回的结果 copy,去除 siblings,然后将 siblings 逐个加入一个缓存队列中,下一个 Decorator 将会逐个处理队列中的 `Text`,且每次处理完后都会清理掉原本的缓存队列。因此,在没有特殊需求的情况下,请总是返回你 Decorator 不关注的信息。
|
||||||
|
|
||||||
一个简单的例子:
|
一个简单的例子:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dependencies {
|
|||||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
shadow('com.maxmind.geoip2:geoip2:4.2.0')
|
shadow('com.maxmind.geoip2:geoip2:4.2.0')
|
||||||
|
shadow('com.google.inject:guice:7.0.0')
|
||||||
}
|
}
|
||||||
configurations.implementation.extendsFrom(configurations.shadow)
|
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;
|
package io.ib67.sfcraft;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.google.inject.*;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|
||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
|
||||||
import io.ib67.sfcraft.config.SFConfig;
|
import io.ib67.sfcraft.config.SFConfig;
|
||||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
import io.ib67.sfcraft.inject.ConfigRoot;
|
||||||
import io.ib67.sfcraft.message.SFMessageDecorator;
|
import lombok.AccessLevel;
|
||||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
|
||||||
import io.ib67.sfcraft.event.SFRandomEventRegistry;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
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.MinecraftServer;
|
||||||
import net.minecraft.server.command.CommandManager;
|
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class SFCraft implements ModInitializer {
|
public class SFCraft extends AbstractModule {
|
||||||
@Getter
|
@Getter(AccessLevel.PUBLIC)
|
||||||
private SFConfig config;
|
static Injector injector;
|
||||||
@Getter
|
/**
|
||||||
private Listener listener;
|
* DO NOT USE THIS DIRECTLY
|
||||||
@Getter
|
* USE {@link io.ib67.sfcraft.inject.MinecraftServerSupplier} IN DI INSTEAD.
|
||||||
private static SFCraft instance;
|
*/
|
||||||
@Getter
|
@Deprecated(forRemoval = true)
|
||||||
private GeoIPService geoIPService;
|
public static MinecraftServer server;
|
||||||
@Getter
|
|
||||||
private Path root;
|
|
||||||
@Getter
|
|
||||||
private MinecraftServer server;
|
|
||||||
@Getter
|
|
||||||
private String motd;
|
|
||||||
@Getter
|
|
||||||
private String updateLog;
|
|
||||||
@Getter
|
|
||||||
private RandomEventRegistry randomEventRegistry;
|
|
||||||
@Getter
|
|
||||||
private MessageDecorator messageDecorator;
|
|
||||||
|
|
||||||
@Override
|
@Provides
|
||||||
|
@ConfigRoot
|
||||||
|
@Singleton
|
||||||
|
public static Path getRoot() {
|
||||||
|
return Path.of("sfcraft");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void onInitialize() {
|
@Singleton
|
||||||
instance = this;
|
@Inject
|
||||||
root = Path.of("sfcraft");
|
private static SFConfig loadConfig(@ConfigRoot Path root, Gson gson) {
|
||||||
if (Files.notExists(root)) {
|
if (Files.notExists(root)) {
|
||||||
Files.createDirectory(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");
|
var cfgFile = root.resolve("config.json");
|
||||||
if (Files.notExists(cfgFile)) {
|
if (Files.notExists(cfgFile)) {
|
||||||
Files.writeString(cfgFile, gson.toJson(new SFConfig()));
|
Files.writeString(cfgFile, gson.toJson(new SFConfig()));
|
||||||
@@ -111,39 +45,4 @@ public class SFCraft implements ModInitializer {
|
|||||||
}
|
}
|
||||||
return gson.fromJson(Files.readString(cfgFile), SFConfig.class);
|
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;
|
import java.util.List;
|
||||||
|
|
||||||
public class SFConfig {
|
public class SFConfig {
|
||||||
|
public boolean enableOfflineExempt = true;
|
||||||
public int maintainceStartHour = 13;
|
public int maintainceStartHour = 13;
|
||||||
public int maintainceEndHour = 17;
|
public int maintainceEndHour = 17;
|
||||||
public boolean disableTemporarily;
|
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;
|
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.exception.GeoIp2Exception;
|
||||||
import com.maxmind.geoip2.record.Country;
|
|
||||||
import io.ib67.sfcraft.SFCraft;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
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.Clock;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
import static java.net.http.HttpResponse.BodyHandlers.ofFile;
|
public interface GeoIPService extends Closeable {
|
||||||
|
Clock clockOf(InetAddress address) throws GeoIp2Exception;
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
package io.ib67.sfcraft.mixin.server;
|
||||||
|
|
||||||
import io.ib67.sfcraft.Helper;
|
import io.ib67.sfcraft.util.Helper;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import net.minecraft.network.QueryableServer;
|
import net.minecraft.network.QueryableServer;
|
||||||
import net.minecraft.network.handler.LegacyQueryHandler;
|
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;
|
package io.ib67.sfcraft.mixin.server;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Either;
|
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.damage.DamageSource;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
@@ -17,13 +17,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||||||
public class PlayerEntityMixin {
|
public class PlayerEntityMixin {
|
||||||
@Inject(method = "onDeath", at = @At("TAIL"))
|
@Inject(method = "onDeath", at = @At("TAIL"))
|
||||||
public void onDeath(DamageSource damageSource, CallbackInfo ci) {
|
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)
|
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
|
||||||
public void onSleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> cir) {
|
public void onSleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> cir) {
|
||||||
var result = SFCraft.getInstance().getListener().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
|
var result = SFCallbacks.PLAYER_SLEEP.invoker().onPlayerSleep((ServerPlayerEntity) (Object) this, pos);
|
||||||
if(result.left().isPresent()){
|
if (result.left().isPresent()) {
|
||||||
cir.setReturnValue(result);
|
cir.setReturnValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.ib67.sfcraft.mixin.server;
|
package io.ib67.sfcraft.mixin.server;
|
||||||
|
|
||||||
import io.ib67.sfcraft.SFCraft;
|
import io.ib67.sfcraft.SFCraft;
|
||||||
|
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||||
import net.minecraft.network.ClientConnection;
|
import net.minecraft.network.ClientConnection;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.ServerMetadata;
|
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;"))
|
@Redirect(method = "onHandshake", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getServerMetadata()Lnet/minecraft/server/ServerMetadata;"))
|
||||||
public ServerMetadata getMetadata(MinecraftServer instance) {
|
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;
|
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.ClientConnection;
|
||||||
import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket;
|
import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.network.ServerLoginNetworkHandler;
|
import net.minecraft.server.network.ServerLoginNetworkHandler;
|
||||||
import net.minecraft.text.Text;
|
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.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
@Mixin(ServerLoginNetworkHandler.class)
|
@Mixin(ServerLoginNetworkHandler.class)
|
||||||
public abstract class ServerLoginNetworkHandlerMixin {
|
public abstract class ServerLoginNetworkHandlerMixin {
|
||||||
@Shadow
|
@Shadow
|
||||||
public abstract void disconnect(Text reason);
|
public abstract void disconnect(Text reason);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
MinecraftServer server;
|
||||||
|
@Unique
|
||||||
private String currentPlayer;
|
private String currentPlayer;
|
||||||
|
|
||||||
@Inject(method = "onHello", at = @At("HEAD"))
|
@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"))
|
@Redirect(method = "onHello", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;isLocal()Z"))
|
||||||
public boolean isLocal(ClientConnection connection) {
|
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;
|
package io.ib67.sfcraft.mixin.server;
|
||||||
|
|
||||||
import io.ib67.sfcraft.SFCraft;
|
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.MessageDecorator;
|
||||||
import net.minecraft.network.message.SignedMessage;
|
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
|
||||||
import org.spongepowered.asm.mixin.injection.*;
|
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)
|
@Mixin(ServerPlayNetworkHandler.class)
|
||||||
public abstract class ServerPlayNetworkHandlerMixin {
|
public abstract class ServerPlayNetworkHandlerMixin {
|
||||||
/**
|
/**
|
||||||
* @author icybear
|
* @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;"))
|
@Redirect(method = "method_44900", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getMessageDecorator()Lnet/minecraft/network/message/MessageDecorator;"))
|
||||||
public MessageDecorator getMessageDecorator(MinecraftServer instance) {
|
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 com.google.inject.Inject;
|
||||||
import io.ib67.sfcraft.SFCraft;
|
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.entity.player.PlayerEntity;
|
||||||
import net.minecraft.network.message.MessageDecorator;
|
import net.minecraft.network.message.MessageDecorator;
|
||||||
import net.minecraft.registry.RegistryKeys;
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.sound.SoundCategory;
|
import net.minecraft.sound.SoundCategory;
|
||||||
import net.minecraft.sound.SoundEvent;
|
import net.minecraft.sound.SoundEvent;
|
||||||
@@ -13,18 +15,29 @@ import net.minecraft.text.Text;
|
|||||||
import net.minecraft.util.Colors;
|
import net.minecraft.util.Colors;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.MatchResult;
|
import java.util.regex.MatchResult;
|
||||||
import java.util.regex.Pattern;
|
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]+)?");
|
private static final Pattern PING = Pattern.compile("(@[\\w]+)?");
|
||||||
|
@Inject
|
||||||
|
MinecraftServerSupplier serverSupplier;
|
||||||
|
@Inject
|
||||||
|
SimpleMessageDecorator messageDecorator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
messageDecorator.registerDecorator(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
if (!SFConsts.USE_AT.hasPermission(sender)) {
|
if (!SFConsts.USE_AT.hasPermission(sender)) {
|
||||||
return message;
|
return message;
|
||||||
@@ -35,7 +48,7 @@ public class PingPlayerFeature implements MessageDecorator {
|
|||||||
var match = PING.matcher(text);
|
var match = PING.matcher(text);
|
||||||
if (!match.find()) return message;
|
if (!match.find()) return message;
|
||||||
var foundPlayers = new HashSet<PlayerEntity>();
|
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)));
|
var r = Text.literal(match.replaceAll(it -> this.matchPlayer(it, names, foundPlayers)));
|
||||||
for (PlayerEntity foundPlayer : foundPlayers) {
|
for (PlayerEntity foundPlayer : foundPlayers) {
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
@@ -58,7 +71,7 @@ public class PingPlayerFeature implements MessageDecorator {
|
|||||||
final var d = r.substring(1);
|
final var d = r.substring(1);
|
||||||
return playerNames.stream()
|
return playerNames.stream()
|
||||||
.filter(it -> it.toLowerCase().startsWith(d))
|
.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()
|
.findFirst()
|
||||||
.orElse(r);
|
.orElse(r);
|
||||||
}
|
}
|
||||||
+17
-5
@@ -1,7 +1,10 @@
|
|||||||
package io.ib67.sfcraft.message.feature;
|
package io.ib67.sfcraft.module.chat;
|
||||||
|
|
||||||
import io.ib67.sfcraft.Helper;
|
import com.google.inject.Inject;
|
||||||
import io.ib67.sfcraft.SFConsts;
|
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.network.message.MessageDecorator;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
@@ -10,15 +13,24 @@ import net.minecraft.text.Text;
|
|||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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
|
@Override
|
||||||
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
public Text decorate(@Nullable ServerPlayerEntity sender, Text message) {
|
||||||
|
if (!isEnabled()) return message;
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
if (!SFConsts.USE_BROADCAST_LOCATION.hasPermission(sender)) {
|
if (!SFConsts.USE_BROADCAST_LOCATION.hasPermission(sender)) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ReplaceHelper.replace(sender, message, ".xyz", SendLocationFeature::generateLocText);
|
return ReplaceHelper.replace(sender, message, ".xyz", ChatSendLocModule::generateLocText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Text generateLocText(ServerPlayerEntity sender) {
|
private static Text generateLocText(ServerPlayerEntity sender) {
|
||||||
+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.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.text.Text;
|
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;
|
package io.ib67.sfcraft.module.randomevt;
|
||||||
|
|
||||||
import io.ib67.sfcraft.event.RandomEvent;
|
|
||||||
|
|
||||||
public interface ActiveEvent {
|
public interface ActiveEvent {
|
||||||
RandomEvent getEvent();
|
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;
|
package io.ib67.sfcraft.module.randomevt;
|
||||||
|
|
||||||
import net.minecraft.registry.RegistryKey;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
public abstract class RandomEvent {
|
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 lombok.RequiredArgsConstructor;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Colors;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Colors;
|
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.network.message.MessageDecorator;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
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);
|
decorators.add(decorator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.ib67.sfcraft.registry.event;
|
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.registry.RegistryKey;
|
||||||
import net.minecraft.world.World;
|
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;
|
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.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.registry.RegistryKeys;
|
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.world.ServerWorld;
|
||||||
import net.minecraft.world.World;
|
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 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 net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class Helper {
|
public class Helper {
|
||||||
public static boolean canBack(ServerPlayerEntity player) {
|
public static boolean canBack(ServerPlayerEntity player) {
|
||||||
@@ -17,9 +24,10 @@ public class Helper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getVersionString(InetSocketAddress address) {
|
public static String getVersionString(InetSocketAddress address) {
|
||||||
|
var geoIp = SFCraft.getInjector().getInstance(GeoIPService.class);
|
||||||
|
var config = SFCraft.getInjector().getInstance(SFConfig.class);
|
||||||
try {
|
try {
|
||||||
var clock = SFCraft.getInstance().getGeoIPService().clockOf(address.getAddress());
|
var clock = geoIp.clockOf(address.getAddress());
|
||||||
var config = SFCraft.getInstance().getConfig();
|
|
||||||
if (config.isClosed(clock)) {
|
if (config.isClosed(clock)) {
|
||||||
return "防沉迷: " + config.maintainceStartHour + "~" + config.maintainceEndHour;
|
return "防沉迷: " + config.maintainceStartHour + "~" + config.maintainceEndHour;
|
||||||
}
|
}
|
||||||
@@ -28,6 +36,13 @@ public class Helper {
|
|||||||
}
|
}
|
||||||
return null;
|
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) {
|
public static int fromRgb(int r, int g, int b) {
|
||||||
return ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
|
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 net.minecraft.entity.Entity;
|
||||||
|
|
||||||
import java.util.Objects;
|
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 lombok.experimental.UtilityClass;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
|
||||||
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class SFConsts {
|
public class SFConsts {
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"environment": "server",
|
"environment": "server",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
"io.ib67.sfcraft.SFCraft"
|
"io.ib67.sfcraft.SFCraftInitializer"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
|
|||||||
Reference in new issue
Block a user