diff --git a/gradle.properties b/gradle.properties index f868b1b1..7e98be3c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,7 @@ emi_version=1.1.22+1.20.1 player_anim_version=1.0.2-rc1+1.20 geckolib_version=4.2.1 curios_version=5.5.0+1.20.1 -lib39_version=0.5.5 +lib39_version=0.5.6 accore_version=1.20.1-26H7 super_lead_version=1.20.1-1.2.1 ## Mod Properties diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/FakePlayerList.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/FakePlayerList.java new file mode 100644 index 00000000..daeaf584 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/FakePlayerList.java @@ -0,0 +1,383 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.content.entity.npc; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.mojang.authlib.GameProfile; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.network.protocol.Packet; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.ServerScoreboard; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.*; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.eroticdungeongame.EroticDungeon; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +public class FakePlayerList extends PlayerList { + private final List npcs = new ArrayList<>(); + private final Map npcsByUUID = Maps.newHashMap(); + private int sendAllNPCInfoIn; + private final List npcsView = java.util.Collections.unmodifiableList(npcs); + @SuppressWarnings("DataFlowIssue") + public FakePlayerList(MinecraftServer server, LayeredRegistryAccess registries, List npcs, Map npcsByUUID) { + super(server, registries, null, -1); + this.npcs.addAll(npcs); + this.npcsByUUID.putAll(npcsByUUID); + } + + private static void noSupportOperate() { + EroticDungeon.getLogger().error("Not supported in FakePlayerList"); + } + + @Override + public void placeNewPlayer(@NotNull Connection netManager, @NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Override + protected void updateEntireScoreboard(@NotNull ServerScoreboard scoreboard, @NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Override + public void addWorldborderListener(@NotNull ServerLevel level) { + noSupportOperate(); + } + + @Nullable + @Override + public CompoundTag load(@NotNull ServerPlayer player) { + noSupportOperate(); + return null; + } + + @Override + protected void save(@NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Override + public void remove(@NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Nullable + @Override + public Component canPlayerLogin(@NotNull SocketAddress socketAddress, @NotNull GameProfile gameProfile) { + noSupportOperate(); + return null; + } + + @Override + public @NotNull ServerPlayer getPlayerForLogin(@NotNull GameProfile profile) { + noSupportOperate(); + return super.getPlayerForLogin(profile); + } + + @Override + public @NotNull ServerPlayer respawn(@NotNull ServerPlayer player, boolean keepEverything) { + noSupportOperate(); + return super.respawn(player, keepEverything); + } + + @Override + public void sendPlayerPermissionLevel(@NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Override + public void tick() { + noSupportOperate(); + } + + @Override + public void broadcastAll(@NotNull Packet packet) { + noSupportOperate(); + } + + @Override + public void broadcastAll(@NotNull Packet packet, @NotNull ResourceKey dimension) { + noSupportOperate(); + } + + @Override + public void broadcastSystemToTeam(@NotNull Player player, @NotNull Component message) { + noSupportOperate(); + } + + @Override + public void broadcastSystemToAllExceptTeam(@NotNull Player player, @NotNull Component message) { + noSupportOperate(); + } + + @Override + public String @NotNull [] getPlayerNamesArray() { + noSupportOperate(); + return super.getPlayerNamesArray(); + } + + @Override + public @NotNull UserBanList getBans() { + noSupportOperate(); + return super.getBans(); + } + + @Override + public @NotNull IpBanList getIpBans() { + noSupportOperate(); + return super.getIpBans(); + } + + @Override + public void op(@NotNull GameProfile profile) { + noSupportOperate(); + } + + @Override + public void deop(@NotNull GameProfile profile) { + noSupportOperate(); + } + + @Override + public boolean isWhiteListed(@NotNull GameProfile profile) { + noSupportOperate(); + return super.isWhiteListed(profile); + } + + @Override + public boolean isOp(@NotNull GameProfile profile) { + noSupportOperate(); + return super.isOp(profile); + } + + @Nullable + @Override + public ServerPlayer getPlayerByName(@NotNull String username) { + return super.getPlayerByName(username); + } + + @Override + public void broadcast(@Nullable Player except, double x, double y, double z, double radius, @NotNull ResourceKey dimension, @NotNull Packet packet) { + noSupportOperate(); + } + + @Override + public void saveAll() { + noSupportOperate(); + } + + @Override + public @NotNull UserWhiteList getWhiteList() { + noSupportOperate(); + return super.getWhiteList(); + } + + @Override + public String @NotNull [] getWhiteListNames() { + noSupportOperate(); + return super.getWhiteListNames(); + } + + @Override + public @NotNull ServerOpList getOps() { + noSupportOperate(); + return super.getOps(); + } + + @Override + public String @NotNull [] getOpNames() { + noSupportOperate(); + return super.getOpNames(); + } + + @Override + public void reloadWhiteList() { + noSupportOperate(); + } + + @Override + public void sendLevelInfo(@NotNull ServerPlayer player, @NotNull ServerLevel level) { + noSupportOperate(); + } + + @Override + public void sendAllPlayerInfo(@NotNull ServerPlayer player) { + noSupportOperate(); + } + + @Override + public int getPlayerCount() { + return this.npcs.size(); + } + + @Override + public int getMaxPlayers() { + noSupportOperate(); + return super.getMaxPlayers(); + } + + @Override + public boolean isUsingWhitelist() { + noSupportOperate(); + return super.isUsingWhitelist(); + } + + @Override + public void setUsingWhiteList(boolean whitelistEnabled) { + noSupportOperate(); + super.setUsingWhiteList(whitelistEnabled); + } + + @Override + public @NotNull List getPlayersWithAddress(@NotNull String address) { + List list = Lists.newArrayList(); + + for(ServerPlayer serverplayer : this.npcs) { + if (serverplayer.getIpAddress().equals(address)) { + list.add(serverplayer); + } + } + + return list; + } + + @Override + public int getViewDistance() { + noSupportOperate(); + return super.getViewDistance(); + } + + @Override + public int getSimulationDistance() { + noSupportOperate(); + return super.getSimulationDistance(); + } + + @Override + public @NotNull MinecraftServer getServer() { + return super.getServer(); + } + + @Nullable + @Override + public CompoundTag getSingleplayerData() { + noSupportOperate(); + return null; + } + + @Override + public void setAllowCheatsForAllPlayers(boolean allowCheatsForAllPlayers) { + noSupportOperate(); + } + + @Override + public void removeAll() { + noSupportOperate(); + } + + @Override + public void broadcastSystemMessage(@NotNull Component message, boolean bypassHiddenChat) { + noSupportOperate(); + } + + @Override + public void broadcastSystemMessage(@NotNull Component serverMessage, @NotNull Function playerMessageFactory, boolean bypassHiddenChat) { + noSupportOperate(); + } + + @Override + public void broadcastChatMessage(@NotNull PlayerChatMessage message, @NotNull CommandSourceStack sender, ChatType.@NotNull Bound boundChatType) { + noSupportOperate(); + } + + @Override + public void broadcastChatMessage(@NotNull PlayerChatMessage message, @NotNull ServerPlayer sender, ChatType.@NotNull Bound boundChatType) { + noSupportOperate(); + } + + @Override + public @NotNull ServerStatsCounter getPlayerStats(@NotNull Player player) { + noSupportOperate(); + return super.getPlayerStats(player); + } + + @Override + public @NotNull PlayerAdvancements getPlayerAdvancements(@NotNull ServerPlayer player) { + noSupportOperate(); + return super.getPlayerAdvancements(player); + } + + @Override + public void setViewDistance(int viewDistance) { + noSupportOperate(); + } + + @Override + public void setSimulationDistance(int simulationDistance) { + noSupportOperate(); + } + + @Override + public @NotNull List getPlayers() { + return this.npcsView; + } + + @Nullable + @Override + public ServerPlayer getPlayer(@NotNull UUID playerUUID) { + return npcsByUUID.get(playerUUID); + } + + @Override + public boolean canBypassPlayerLimit(@NotNull GameProfile profile) { + noSupportOperate(); + return super.canBypassPlayerLimit(profile); + } + + @Override + public void reloadResources() { + noSupportOperate(); + } + + @Override + public boolean isAllowCheatsForAllPlayers() { + noSupportOperate(); + return super.isAllowCheatsForAllPlayers(); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCPlayerList.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCPlayerList.java index 91ad58c9..02aa29cc 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCPlayerList.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCPlayerList.java @@ -23,21 +23,23 @@ import com.mojang.logging.LogUtils; import com.mojang.serialization.Dynamic; import io.netty.buffer.Unpooled; import net.minecraft.ChatFormatting; +import net.minecraft.Util; import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.RegistryAccess; import net.minecraft.core.RegistrySynchronization; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.NbtUtils; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.*; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.*; import net.minecraft.network.protocol.status.ServerStatus; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.RegistryLayer; import net.minecraft.server.ServerScoreboard; import net.minecraft.server.level.ServerLevel; @@ -45,7 +47,6 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.server.players.GameProfileCache; import net.minecraft.server.players.PlayerList; -import net.minecraft.stats.ServerStatsCounter; import net.minecraft.stats.Stats; import net.minecraft.tags.TagNetworkSerialization; import net.minecraft.world.effect.MobEffectInstance; @@ -60,23 +61,26 @@ import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.scores.Objective; import net.minecraft.world.scores.PlayerTeam; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.OnDatapackSyncEvent; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler; +import top.r3944realms.eroticdungeongame.core.network.NPCEmptyClientConnection; import top.r3944realms.eroticdungeongame.core.network.toClient.NPCInfoUpdatePacket; import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage; +import top.r3944realms.lib39.util.nbt.NBTReader; import javax.annotation.Nullable; -import java.text.SimpleDateFormat; import java.time.Instant; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; public class NPCPlayerList { private static final Logger LOGGER = LogUtils.getLogger(); private static final int SEND_PLAYER_INFO_INTERVAL = 600; - private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); private final LayeredRegistryAccess registries; private final RegistryAccess.Frozen synchronizedRegistries; private final NPCDataStorage npcIo; @@ -86,13 +90,13 @@ public class NPCPlayerList { private int sendAllNPCInfoIn; private final List npcsView = java.util.Collections.unmodifiableList(npcs); public static final Component CHAT_FILTERED_FULL = Component.translatable("chat.filtered_full"); - public NPCPlayerList(MinecraftServer server, LayeredRegistryAccess registries, NPCDataStorage npcDataStorage, int maxPlayers) { + public NPCPlayerList(MinecraftServer server, LayeredRegistryAccess registries, NPCDataStorage npcDataStorage) { this.server = server; this.registries = registries; this.synchronizedRegistries = (new RegistryAccess.ImmutableRegistryAccess(RegistrySynchronization.networkedRegistries(registries))).freeze(); this.npcIo = npcDataStorage; } - public void placeNewNPC(Connection netManager, NPCServerPlayer npc) { + public void placeNewNPC(Connection netManager, @NotNull NPCServerPlayer npc) { GameProfile gameprofile = npc.getGameProfile(); GameProfileCache gameprofilecache = this.server.getProfileCache(); String s; @@ -106,14 +110,7 @@ public class NPCPlayerList { CompoundTag compoundtag = this.load(npc); //noinspection deprecation ResourceKey resourcekey = compoundtag != null ? DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundtag.get("Dimension"))).resultOrPartial(LOGGER::error).orElse(Level.OVERWORLD) : Level.OVERWORLD; - ServerLevel serverlevel = this.server.getLevel(resourcekey); - ServerLevel serverlevel1; - if (serverlevel == null) { - LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey); - serverlevel1 = this.server.overworld(); - } else { - serverlevel1 = serverlevel; - } + ServerLevel serverlevel1 = getServerLevel(resourcekey); npc.setServerLevel(serverlevel1); String s1 = "local"; @@ -135,8 +132,10 @@ public class NPCPlayerList { servergamepacketlistenerimpl.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked())); servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(npc.getAbilities())); servergamepacketlistenerimpl.send(new ClientboundSetCarriedItemPacket(npc.getInventory().selected)); - // todo: 数据同步事件(对于NPC类) 可以搞个继承类复用这个事件 -// net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.OnDatapackSyncEvent(this, npc)); + + //数据同步事件(对于NPC类) 搞个继承类复用这个事件 (如果有模组用这个PlayerList搞其它事情可能会导致BUG) + MinecraftForge.EVENT_BUS.post(new OnDatapackSyncEvent(this.toFakePlayerList(), npc)); + servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes())); servergamepacketlistenerimpl.send(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); this.sendPlayerPermissionLevel(npc); @@ -145,7 +144,7 @@ public class NPCPlayerList { this.updateEntireScoreboard(serverlevel1.getScoreboard(), npc); this.server.invalidateStatus(); MutableComponent mutablecomponent; - if (npc.getGameProfile().getName().equalsIgnoreCase(s)) { //todo npc + if (npc.getGameProfile().getName().equalsIgnoreCase(s)) { //todo npc 语言 mutablecomponent = Component.translatable("multiplayer.player.joined", npc.getDisplayName()); } else { mutablecomponent = Component.translatable("multiplayer.player.joined.renamed", npc.getDisplayName(), s); @@ -157,7 +156,7 @@ public class NPCPlayerList { if (serverstatus != null) { npc.sendServerStatus(serverstatus); } - //todo 实现网络传递 + // 实现网络传递 EDGNetworkHandler.sendToPlayer(NPCInfoUpdatePacket.createNPCPlayerInitializing(this.npcs), npc); this.npcs.add(npc); this.npcsByUUID.put(npc.getUUID(), npc); @@ -207,6 +206,79 @@ public class NPCPlayerList { npc.initInventoryMenu(); net.minecraftforge.event.ForgeEventFactory.firePlayerLoggedIn(npc); } + + private @NotNull ServerLevel getServerLevel(ResourceKey resourcekey) { + ServerLevel serverlevel = this.server.getLevel(resourcekey); + ServerLevel serverlevel1; + if (serverlevel == null) { + LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey); + serverlevel1 = this.server.overworld(); + } else { + serverlevel1 = serverlevel; + } + return serverlevel1; + } + + /** + * 加载所有已存在的 NPC(从磁盘) + */ + public void loadAllNPCs() { + String[] seenNPCs = this.npcIo.getSeenNPCs(); + LOGGER.info("Loading {} existing NPCs from disk", seenNPCs.length); + + for (String uuidStr : seenNPCs) { + try { + UUID uuid = UUID.fromString(uuidStr); + if (loadNPC(uuid)) { + LOGGER.info("Loaded NPC {} from disk", uuidStr); + } + } catch (Exception e) { + LOGGER.error("Failed to load NPC with UUID: {}", uuidStr, e); + } + } + } + /** + * 加载指定的 NPC + * @param uuid NPC 的 UUID + * @return 是否加载成功 + */ + public boolean loadNPC(UUID uuid) { + // 检查是否已经加载 + if (this.npcsByUUID.containsKey(uuid)) { + LOGGER.debug("NPC {} already loaded", uuid); + return true; + } + + try { + NPCServerPlayer placeholder = new NPCServerPlayer(server, server.overworld(), new GameProfile(Util.NIL_UUID, "placeholder")); + placeholder.setUUID(uuid); + NPCServerPlayer npcServerPlayer = this.npcIo.load(uuid, this::createNPCFromData); + if (npcServerPlayer != null) { + placeNewNPC(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), npcServerPlayer); + return true; + } + } catch (Exception e) { + LOGGER.error("Error loading NPC with UUID: {}", uuid, e); + } + return false; + } + public @NotNull NPCServerPlayer createNPCFromData(CompoundTag npcData) { + AtomicReference npcName = new AtomicReference<>(); + AtomicReference gameProfile = new AtomicReference<>(); + AtomicReference> dimension = new AtomicReference<>(Level.OVERWORLD); + //noinspection deprecation + NBTReader.of(npcData) + .string("NpcName", npcName::set) + .compound("NpcGameProfile", data -> gameProfile.set(NbtUtils.readGameProfile(data))) + .compound("Dimension", data -> dimension.set(DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, data.get("Dimension"))) + .resultOrPartial(LOGGER::error) + .orElse(Level.OVERWORLD))); + ResourceKey levelResourceKey = dimension.get(); + ServerLevel serverlevel1 = getServerLevel(levelResourceKey); + + return new NPCServerPlayer(server, serverlevel1, gameProfile.get()); + } + public void sendPlayerPermissionLevel(@NotNull ServerPlayer player) { GameProfile gameprofile = player.getGameProfile(); int i = this.server.getProfilePermissions(gameprofile); @@ -265,16 +337,17 @@ public class NPCPlayerList { * Called during player login. Reads the player information from disk. */ @Nullable - public CompoundTag load(@NotNull NPCServerPlayer player) { + public CompoundTag load(@NotNull NPCServerPlayer npc) { CompoundTag compoundtag = this.server.getWorldData().getLoadedPlayerTag(); CompoundTag compoundtag1; - if (this.server.isSingleplayerOwner(player.getGameProfile()) && compoundtag != null) { + if (this.server.isSingleplayerOwner(npc.getGameProfile()) && compoundtag != null) { compoundtag1 = compoundtag; - player.load(compoundtag); + npc.load(compoundtag); LOGGER.debug("loading single npc"); -// net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(player, this.npcIo, player.getUUID().toString()); + // 应该不会有模组干这个吧? + net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(npc, this.npcIo.getNPCDataFolder(), npc.getUUID().toString()); } else { - compoundtag1 = this.npcIo.load(player); + compoundtag1 = this.npcIo.load(npc); } return compoundtag1; @@ -298,6 +371,11 @@ public class NPCPlayerList { } + public void delete(NPCServerPlayer npc) { + remove(npc); + npcIo.deleteNPCData(npc); + } + public void remove(NPCServerPlayer npc) { net.minecraftforge.event.ForgeEventFactory.firePlayerLoggedOut(npc); ServerLevel serverlevel = npc.serverLevel(); @@ -324,7 +402,6 @@ public class NPCPlayerList { if (serverplayer == npc) { this.npcsByUUID.remove(uuid); } - this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(npc.getUUID()))); } @@ -347,7 +424,7 @@ public class NPCPlayerList { } public void tick() { - if (++this.sendAllNPCInfoIn > 600) { + if (++this.sendAllNPCInfoIn > SEND_PLAYER_INFO_INTERVAL) { EDGNetworkHandler.sendToAllPlayer(new NPCInfoUpdatePacket(EnumSet.of(NPCInfoUpdatePacket.Action.UPDATE_LATENCY), this.npcs)); this.sendAllNPCInfoIn = 0; } @@ -434,4 +511,8 @@ public class NPCPlayerList { public MinecraftServer getServer() { return this.server; } + + public @NotNull FakePlayerList toFakePlayerList() { + return new FakePlayerList(this.server, this.registries, this.npcs, this.npcsByUUID); + } } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCRemotePlayer.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCRemotePlayer.java index 95e81e0d..2c584698 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCRemotePlayer.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCRemotePlayer.java @@ -17,11 +17,27 @@ package top.r3944realms.eroticdungeongame.content.entity.npc; import com.mojang.authlib.GameProfile; +import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.client.player.RemotePlayer; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.eroticdungeongame.util.IEDGClientPacketListener; public class NPCRemotePlayer extends RemotePlayer implements INPCPlayer { + private PlayerInfo npcPlayerInfo; public NPCRemotePlayer(ClientLevel clientLevel, GameProfile gameProfile) { super(clientLevel, gameProfile); } + + @Nullable + @Override + protected PlayerInfo getPlayerInfo() { + if (this.npcPlayerInfo == null) { + if (Minecraft.getInstance().getConnection() instanceof IEDGClientPacketListener iedgClientPacketListener) { + this.npcPlayerInfo = iedgClientPacketListener.getNPCPlayerInfo(this.getUUID()); + } + } + return this.npcPlayerInfo; + } } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java index 2909bfdf..602674a6 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/npc/NPCServerPlayer.java @@ -21,11 +21,11 @@ import dev.dubhe.curtain.utils.Messenger; import net.minecraft.core.BlockPos; import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; -import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; -import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; -import net.minecraft.network.protocol.game.ServerboundClientCommandPacket; +import net.minecraft.network.protocol.game.*; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.TickTask; @@ -46,12 +46,24 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import top.r3944realms.eroticdungeongame.EroticDungeon; import top.r3944realms.eroticdungeongame.core.network.NPCEmptyClientConnection; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; +import top.r3944realms.lib39.util.nbt.NBTWriter; -import java.util.Iterator; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { + public static final String PREFIX = "[NPC]"; + public String npcNameWithoutPrefix; + + public String getNpcNameWithoutPrefix() { + return npcNameWithoutPrefix; + } + + public void setNpcNameWithoutPrefix(String npcNameWithoutPrefix) { + this.npcNameWithoutPrefix = npcNameWithoutPrefix; + } + public Runnable fixStartingPosition = () -> { }; public NPCServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile) { @@ -86,14 +98,14 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { instance.fixStartingPosition = () -> { instance.moveTo(x, y, z, (float) yaw, (float) pitch); }; - server.getPlayerList().placeNewPlayer(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), instance); + ((IEDGMinecraftServer)server).getNPCPlayerList().placeNewNPC(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), instance); instance.teleportTo(worldIn, x, y, z, (float) yaw, (float) pitch); instance.setHealth(20.0F); instance.unsetRemoved(); instance.setMaxUpStep(0.6F); instance.gameMode.changeGameModeForPlayer(gamemode); - server.getPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte)((int)(instance.yHeadRot * 256.0F / 360.0F))), dimensionId); - server.getPlayerList().broadcastAll(new ClientboundTeleportEntityPacket(instance), dimensionId); + ((IEDGMinecraftServer)server).getNPCPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte)((int)(instance.yHeadRot * 256.0F / 360.0F))), dimensionId); + ((IEDGMinecraftServer)server).getNPCPlayerList().broadcastAll(new ClientboundTeleportEntityPacket(instance), dimensionId); instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, (byte)127); instance.getAbilities().flying = isflying; return instance; @@ -104,14 +116,15 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { return null; } - - public void onEquipItem(EquipmentSlot p_238393_, ItemStack p_238394_, ItemStack p_238395_) { + @Override + public void onEquipItem(@NotNull EquipmentSlot equipmentSlot, @NotNull ItemStack oldItem, @NotNull ItemStack newItem) { if (!this.isUsingItem()) { - super.onEquipItem(p_238393_, p_238394_, p_238395_); + super.onEquipItem(equipmentSlot, oldItem, newItem); } } + @Override public void kill() { this.kill(Messenger.s("Killed")); } @@ -119,7 +132,9 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { public void kill(Component reason) { this.shakeOff(); this.server.tell(new TickTask(this.server.getTickCount(), () -> { - this.connection.onDisconnect(reason); + if (this.getServer() instanceof IEDGMinecraftServer iedgMinecraftServer) { + iedgMinecraftServer.getNPCPlayerList().delete(this); + } })); } @@ -128,10 +143,7 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { this.stopRiding(); } - Iterator var1 = this.getIndirectPassengers().iterator(); - - while(var1.hasNext()) { - Entity passenger = (Entity)var1.next(); + for (Entity passenger : this.getIndirectPassengers()) { if (passenger instanceof Player) { passenger.stopRiding(); } @@ -139,6 +151,7 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { } + @Override public void die(@NotNull DamageSource damageSource) { this.shakeOff(); super.die(damageSource); @@ -150,6 +163,11 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { @Override public void addAdditionalSaveData(@NotNull CompoundTag compound) { super.addAdditionalSaveData(compound); + String npcName = getName().getString(); + String npcNameWithoutPrefix = npcName.substring(PREFIX.length()); + NBTWriter.of(compound) + .string("NpcName", npcNameWithoutPrefix) + .compound("NpcGameProfile", NbtUtils.writeGameProfile(new CompoundTag(), this.getGameProfile())); } @Override @@ -164,7 +182,8 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { @Override public @NotNull Component getName() { - return Component.literal("[NPC]").append(super.getName()); + + return Component.literal(PREFIX).append(super.getName()); } @Override @@ -182,10 +201,11 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { } + @Override public Entity changeDimension(@NotNull ServerLevel level) { super.changeDimension(level); if (this.wonGame) { - ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(net.minecraft.network.protocol.game.ServerboundClientCommandPacket.Action.PERFORM_RESPAWN); + ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN); this.connection.handleClientCommand(packet); } @@ -196,8 +216,16 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer { return this.connection.player; } + @Override protected void checkFallDamage(double y, boolean onGround, @NotNull BlockState state, @NotNull BlockPos pos) { this.doCheckFallDamage(this.getX(), y, this.getZ(), onGround); } + @Override + public @NotNull Packet getAddEntityPacket() { + ClientboundAddPlayerPacket clientboundAddPlayerPacket = new ClientboundAddPlayerPacket(this); + clientboundAddPlayerPacket.playerId = getUUID(); + return clientboundAddPlayerPacket; + } + } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/core/compat/SuperLeadRopeCompat.java b/src/main/java/top/r3944realms/eroticdungeongame/core/compat/SuperLeadRopeCompat.java index 0223ed6d..ef7d6689 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/core/compat/SuperLeadRopeCompat.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/core/compat/SuperLeadRopeCompat.java @@ -96,7 +96,7 @@ public class SuperLeadRopeCompat implements IForgeCompat { } @OnlyIn(Dist.CLIENT) - private void onRegisterKeyBind(RegisterKeyMappingsEvent event) { + private void onRegisterKeyBind(@NotNull RegisterKeyMappingsEvent event) { event.register(EDGKeyBindings.KEY_BIND); } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/core/event/CommonHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/core/event/CommonHandler.java index 549dfbb2..62fbc14a 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/core/event/CommonHandler.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/core/event/CommonHandler.java @@ -19,6 +19,7 @@ package top.r3944realms.eroticdungeongame.core.event; import com.google.common.collect.ImmutableList; import net.minecraft.client.RecipeBookCategories; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.npc.VillagerTrades; import net.minecraft.world.entity.player.Player; import net.minecraftforge.api.distmarker.Dist; @@ -30,15 +31,18 @@ import net.minecraftforge.event.AttachCapabilitiesEvent; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.jetbrains.annotations.NotNull; import top.r3944realms.eroticdungeongame.EroticDungeon; import top.r3944realms.eroticdungeongame.content.EDGVillagerTrades; import top.r3944realms.eroticdungeongame.content.command.EDGCommand; +import top.r3944realms.eroticdungeongame.content.entity.npc.INPCPlayer; import top.r3944realms.eroticdungeongame.content.recipe.DungeonCraftingBookCategory; import top.r3944realms.eroticdungeongame.content.recipe.DungeonRecipe; import top.r3944realms.eroticdungeongame.content.recipe.EDGRecipeBookTypes; @@ -46,11 +50,14 @@ import top.r3944realms.eroticdungeongame.content.recipe.EDGRecipeTypeCategories; import top.r3944realms.eroticdungeongame.core.capability.DungeonDataSyncManager; import top.r3944realms.eroticdungeongame.core.capability.PlayerDungeonDataProvider; import top.r3944realms.eroticdungeongame.core.compat.*; +import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler; +import top.r3944realms.eroticdungeongame.core.network.toClient.NPCInfoUpdatePacket; import top.r3944realms.eroticdungeongame.core.register.EDGCapabilities; import top.r3944realms.eroticdungeongame.core.register.EDGRecipeTypes; import top.r3944realms.eroticdungeongame.datagen.EDGDataGenEvent; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent; -import top.r3944realms.lib39.core.compat.CompatManager; +import top.r3944realms.lib39.core.compat.ForgeCompatManager; import top.r3944realms.lib39.core.event.CommonEventHandler; import java.util.List; @@ -65,6 +72,13 @@ public class CommonHandler { EDGCapabilities.attachCapability(event); } + @SubscribeEvent + public static void onServerStarted(@NotNull ServerStartedEvent event) { + if (event.getServer() instanceof IEDGMinecraftServer iedgMinecraftServer) { + iedgMinecraftServer.getNPCPlayerList().loadAllNPCs(); + } + } + @SubscribeEvent public static void syncCapabilities(SyncManagerRegisterEvent event) { dungeonDataSyncManager = new DungeonDataSyncManager(); @@ -87,6 +101,11 @@ public class CommonHandler { Player player = event.getEntity(); if (player == null || player.level().isClientSide()) return; DungeonDataSyncManager.login(player); + if (player.getServer() instanceof IEDGMinecraftServer iedgMinecraftServer && player instanceof ServerPlayer serverPlayer && !(player instanceof INPCPlayer)) { + EDGNetworkHandler.sendToPlayer(NPCInfoUpdatePacket.createNPCPlayerInitializing(iedgMinecraftServer.getNPCPlayerList().getPlayers()), serverPlayer); + serverPlayer.serverLevel().getChunkSource().removeEntity(); + } + } @SubscribeEvent @@ -108,7 +127,7 @@ public class CommonHandler { * * @return the compat manager */ - public static CompatManager getOrCreateCompatManager() { + public static ForgeCompatManager getOrCreateCompatManager() { if (compatManager == null) { synchronized (CommonEventHandler.Mod.class) { if (compatManager == null) { @@ -122,7 +141,7 @@ public class CommonHandler { /** * The Compat manager. */ - static volatile CompatManager compatManager; + static volatile ForgeCompatManager compatManager; /** @@ -133,7 +152,7 @@ public class CommonHandler { @SubscribeEvent public static void onConstructMod(FMLConstructModEvent event) { event.enqueueWork(() -> { - CompatManager orCreateCompatManager = Mod.getOrCreateCompatManager(); + ForgeCompatManager orCreateCompatManager = Mod.getOrCreateCompatManager(); orCreateCompatManager .registerCompat(CarryOnCompat.ID, CarryOnCompat.INSTANCE); orCreateCompatManager @@ -142,16 +161,16 @@ public class CommonHandler { .registerCompat(EmoteCraftCompat.ID, EmoteCraftCompat.INSTANCE); orCreateCompatManager .registerCompat(TouhouLittleMaidCompat.ID, TouhouLittleMaidCompat.INSTANCE); - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { - orCreateCompatManager - .registerCompat(FreecamCompat.ID, FreecamCompat.INSTANCE); - orCreateCompatManager - .registerCompat(RealCameraCompat.ID, RealCameraCompat.INSTANCE); - orCreateCompatManager - .registerCompat(FirstPersonModelAndRealCameraCompat.ID, FirstPersonModelAndRealCameraCompat.INSTANCE); - } - ); - orCreateCompatManager.initialize(); + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + orCreateCompatManager + .registerCompat(FreecamCompat.ID, FreecamCompat.INSTANCE); + orCreateCompatManager + .registerCompat(RealCameraCompat.ID, RealCameraCompat.INSTANCE); + orCreateCompatManager + .registerCompat(FirstPersonModelAndRealCameraCompat.ID, FirstPersonModelAndRealCameraCompat.INSTANCE); + } + ); + orCreateCompatManager.initialize(); }); } @SubscribeEvent diff --git a/src/main/java/top/r3944realms/eroticdungeongame/core/network/toClient/NPCInfoUpdatePacket.java b/src/main/java/top/r3944realms/eroticdungeongame/core/network/toClient/NPCInfoUpdatePacket.java index b99c4a96..8cc2ae8e 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/core/network/toClient/NPCInfoUpdatePacket.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/core/network/toClient/NPCInfoUpdatePacket.java @@ -16,7 +16,6 @@ package top.r3944realms.eroticdungeongame.core.network.toClient; -import com.eliotlash.mclib.math.functions.limit.Min; import com.mojang.authlib.GameProfile; import net.minecraft.Optionull; import net.minecraft.client.Minecraft; @@ -26,7 +25,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.RemoteChatSession; import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.GameType; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -63,7 +61,7 @@ public final class NPCInfoUpdatePacket { } public NPCInfoUpdatePacket(@NotNull FriendlyByteBuf buf) { - this.actions = EnumSet.noneOf(Action.class); + this.actions = buf.readEnumSet(Action.class); this.entries = buf.readList((read) -> { EntryBuilder builder = new EntryBuilder(read.readUUID()); @@ -77,7 +75,7 @@ public final class NPCInfoUpdatePacket { public void write(@NotNull FriendlyByteBuf buffer) { buffer.writeEnumSet(this.actions, Action.class); buffer.writeCollection(this.entries, (buf, entry) -> { - buf.writeUUID(entry.profileId()); + buf.writeUUID(entry.uuid()); for(Action action : this.actions) { action.writer.write(buf, entry); @@ -97,12 +95,12 @@ public final class NPCInfoUpdatePacket { if (clientPacketListener instanceof IEDGClientPacketListener iedgClientPacketListener) { for (Entry entry : packet.newEntries()) { PlayerInfo npcInfo = new PlayerInfo(entry.profile, false); - iedgClientPacketListener.getNPCPlayerInfoMap().putIfAbsent(entry.profileId(), npcInfo); + iedgClientPacketListener.getNPCPlayerInfoMap().putIfAbsent(entry.uuid(), npcInfo); } for (Entry entry : packet.entries) { - PlayerInfo npcInfo = iedgClientPacketListener.getNPCPlayerInfoMap().get(entry.profileId); + PlayerInfo npcInfo = iedgClientPacketListener.getNPCPlayerInfoMap().get(entry.uuid); if (npcInfo == null) { - EroticDungeon.getLogger().warn("Ignoring npc player info update for unknown npc player {}", entry.profileId()); + EroticDungeon.getLogger().warn("Ignoring npc player info update for unknown npc player {}", entry.uuid()); } else { for(Action action : packet.actions()) { applyPlayerInfoUpdate(action, entry, npcInfo); @@ -121,13 +119,13 @@ public final class NPCInfoUpdatePacket { switch (action) { case INITIALIZE_CHAT: ClientboundPlayerInfoUpdatePacket.Entry copyEntry = new ClientboundPlayerInfoUpdatePacket.Entry( - entry.profileId, entry.profile, entry.listed, entry.latency, entry.gameMode, entry.displayName, entry.chatSession + entry.uuid, entry.profile, entry.listed, entry.latency, entry.gameMode, entry.displayName, entry.chatSession ); clientPacketListener.initializeChatSession(copyEntry, playerInfo); break; case UPDATE_GAME_MODE: - if (playerInfo.getGameMode() != entry.gameMode() && minecraft.player != null && minecraft.player.getUUID().equals(entry.profileId())) { + if (playerInfo.getGameMode() != entry.gameMode() && minecraft.player != null && minecraft.player.getUUID().equals(entry.uuid())) { minecraft.player.onGameModeChanged(entry.gameMode()); } @@ -204,7 +202,7 @@ public final class NPCInfoUpdatePacket { } - public record Entry(UUID profileId, GameProfile profile, boolean listed, int latency, GameType gameMode, + public record Entry(UUID uuid, GameProfile profile, boolean listed, int latency, GameType gameMode, @Nullable Component displayName, @Nullable RemoteChatSession.Data chatSession) { Entry(@NotNull NPCServerPlayer npc) { this(npc.getUUID(), npc.getGameProfile(), true, npc.latency, npc.gameMode.getGameModeForPlayer(), npc.getTabListDisplayName(), Optionull.map(npc.getChatSession(), RemoteChatSession::asData)); diff --git a/src/main/java/top/r3944realms/eroticdungeongame/core/storage/NPCDataStorage.java b/src/main/java/top/r3944realms/eroticdungeongame/core/storage/NPCDataStorage.java index 7cb56a56..a27fdfcc 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/core/storage/NPCDataStorage.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/core/storage/NPCDataStorage.java @@ -30,6 +30,9 @@ import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer; import javax.annotation.Nullable; import java.io.File; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; public class NPCDataStorage { private static final Logger LOGGER = LogUtils.getLogger(); @@ -41,6 +44,65 @@ public class NPCDataStorage { this.npcDir = levelStorageAccess.getLevelPath(NPC_DATA_DIR).toFile(); this.npcDir.mkdirs(); } + + + /** + * 删除指定 NPC 的数据文件 + * @param npcUUID 要删除的 NPC 的 UUID + * @return 是否删除成功 + */ + public boolean deleteNPCData(UUID npcUUID) { + if (npcUUID == null) { + LOGGER.warn("Cannot delete NPC data: UUID is null"); + return false; + } + + try { + File dataFile = new File(this.npcDir, npcUUID + ".dat"); + File oldFile = new File(this.npcDir, npcUUID + ".dat_old"); + + boolean deleted = false; + + // 删除主数据文件 + if (dataFile.exists()) { + deleted = dataFile.delete(); + if (deleted) { + LOGGER.info("Deleted NPC data file: {}", dataFile.getName()); + } else { + LOGGER.warn("Failed to delete NPC data file: {}", dataFile.getName()); + } + } + + // 删除备份文件 + if (oldFile.exists()) { + boolean oldDeleted = oldFile.delete(); + if (oldDeleted) { + LOGGER.debug("Deleted NPC backup file: {}", oldFile.getName()); + } + } + + return deleted; + + } catch (Exception e) { + LOGGER.error("Failed to delete NPC data for UUID: {}", npcUUID, e); + return false; + } + } + + /** + * 删除指定 NPC 的数据文件(通过 NPCServerPlayer 对象) + * @param npc 要删除的 NPC + * @return 是否删除成功 + */ + public boolean deleteNPCData(NPCServerPlayer npc) { + if (npc == null) { + LOGGER.warn("Cannot delete NPC data: NPC is null"); + return false; + } + + return deleteNPCData(npc.getUUID()); + } + public void save(NPCServerPlayer player) { try { CompoundTag compoundtag = player.saveWithoutId(new CompoundTag()); @@ -55,7 +117,27 @@ public class NPCDataStorage { } } + @Nullable + public NPCServerPlayer load(UUID uuid, Function loader) { + CompoundTag compoundtag = null; + try { + File file1 = new File(this.npcDir, uuid + ".dat"); + if (file1.exists() && file1.isFile()) { + compoundtag = NbtIo.readCompressed(file1); + } + } catch (Exception exception) { + LOGGER.warn("Failed to load player data for {}", uuid, exception); + } + NPCServerPlayer npcServerPlayer = loader.apply(compoundtag); + if (compoundtag != null) { + int i = NbtUtils.getDataVersion(compoundtag, -1); + npcServerPlayer.load(DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundtag, i)); + } + + net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(npcServerPlayer, npcDir, uuid.toString()); + return npcServerPlayer; + } @Nullable public CompoundTag load(NPCServerPlayer npcServerPlayer) { CompoundTag compoundtag = null; @@ -73,23 +155,24 @@ public class NPCDataStorage { int i = NbtUtils.getDataVersion(compoundtag, -1); npcServerPlayer.load(DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundtag, i)); } -// net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(npcServerPlayer, npcDir, npcServerPlayer.getStringUUID()); + net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(npcServerPlayer, npcDir, npcServerPlayer.getStringUUID()); return compoundtag; } public String[] getSeenNPCs() { String[] astring = this.npcDir.list(); + Set ret = new HashSet<>(); if (astring == null) { astring = new String[0]; - } - - for(int i = 0; i < astring.length; ++i) { - if (astring[i].endsWith(".dat")) { - astring[i] = astring[i].substring(0, astring[i].length() - 4); + } else { + for (String s : astring) { + if (s.endsWith(".dat") && !s.endsWith(".dat_old")) { + ret.add(s.substring(0, s.length() - 4)); + } } + astring = ret.toArray(new String[0]); } - return astring; } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinClientPacketListener.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinClientPacketListener.java index 2de6787e..9b4c4f80 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinClientPacketListener.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinClientPacketListener.java @@ -19,8 +19,10 @@ package top.r3944realms.eroticdungeongame.mixin.minecraft; import com.google.common.collect.Maps; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.network.chat.Component; @@ -28,6 +30,7 @@ import net.minecraft.network.protocol.PacketUtils; import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket; import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -39,6 +42,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import top.r3944realms.eroticdungeongame.client.EDGKeyBindings; import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCRemotePlayer; import top.r3944realms.eroticdungeongame.util.IEDGClientPacketListener; import javax.annotation.Nullable; @@ -61,6 +65,7 @@ public abstract class MixinClientPacketListener implements IEDGClientPacketListe @Shadow @Nullable public abstract PlayerInfo getPlayerInfo(UUID uniqueId); @Shadow @Final private Set listedPlayers; + @Shadow private ClientLevel level; @Unique private Entity edg$cachedMount = null; @@ -84,15 +89,51 @@ public abstract class MixinClientPacketListener implements IEDGClientPacketListe PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft); PlayerInfo playerinfo = this.getPlayerInfo(packet.getPlayerId()); if (playerinfo == null) { - + PlayerInfo npcPlayerInfo = getNPCPlayerInfo(packet.getPlayerId()); + if (npcPlayerInfo == null) { + original.call(packet); + } else { + double d0 = packet.getX(); + double d1 = packet.getY(); + double d2 = packet.getZ(); + float f = (float)(packet.getyRot() * 360) / 256.0F; + float f1 = (float)(packet.getxRot() * 360) / 256.0F; + int i = packet.getEntityId(); + assert this.minecraft.level != null; + NPCRemotePlayer npcRemotePlayer = new NPCRemotePlayer(this.minecraft.level, npcPlayerInfo.getProfile()); + npcRemotePlayer.setUUID(packet.getPlayerId()); + npcRemotePlayer.setId(i); + npcRemotePlayer.syncPacketPositionCodec(d0, d1, d2); + npcRemotePlayer.absMoveTo(d0, d1, d2, f, f1); + npcRemotePlayer.setOldPosAndRot(); + this.level.addPlayer(i, npcRemotePlayer); + } } else original.call(packet); } + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + @WrapOperation( + method = "handlePlayerInfoUpdate", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;" + ) + ) + private V edg$handlePlayerInfoUpdate(Map instance, Object object, @NotNull Operation original) { + V call = original.call(instance, object); + if (call == null) { + call = (V) getNPCPlayerInfoMap().get(object); + } + return call; + } + + @SuppressWarnings("AddedMixinMembersNamePattern") @Override public Map getNPCPlayerInfoMap() { return edg$npcPlayerInfoMap; } + @SuppressWarnings("AddedMixinMembersNamePattern") @Override public Set getListedNPCPlayers() { return listedPlayers; diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinDedicatedServer.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinDedicatedServer.java new file mode 100644 index 00000000..020980aa --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinDedicatedServer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.mixin.minecraft; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; + +@Mixin(DedicatedServer.class) +public abstract class MixinDedicatedServer implements IEDGMinecraftServer { + @WrapOperation( + method = "initServer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/dedicated/DedicatedServer;setPlayerList(Lnet/minecraft/server/players/PlayerList;)V" + ) + ) + void edg$initServer$setNPCList(DedicatedServer instance, PlayerList playerList, Operation original) { + original.call(instance, playerList); + this.setNPCPlayerList(new NPCPlayerList(instance, instance.registries(), this.getNPCDataStorage())); + + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinEntitySelector.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinEntitySelector.java new file mode 100644 index 00000000..81b67777 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinEntitySelector.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.mixin.minecraft; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; + +@Mixin(EntitySelector.class) +public class MixinEntitySelector { + @Shadow @Final private Predicate predicate; + + @Shadow @Final @Nullable private UUID entityUUID; + + @WrapMethod( + method = "findEntitiesRaw" + ) + private @NotNull List edg$findEntitiesRaw$findNPCs(CommandSourceStack source, @NotNull Operation> original) { + List ret = new ArrayList<>(original.call(source)); + if (source.getServer() instanceof IEDGMinecraftServer iedgMinecraftServer) { + NPCPlayerList npcPlayerList = iedgMinecraftServer.getNPCPlayerList(); + if (npcPlayerList != null) { + for (NPCServerPlayer npc : npcPlayerList.getPlayers()) { + if (Objects.equals(entityUUID, npc.getUUID()) && predicate.test(npc)) { + ret.add(npc); + } + } + } + } + return ret; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinGameTestServer.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinGameTestServer.java new file mode 100644 index 00000000..215944bd --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinGameTestServer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.mixin.minecraft; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.gametest.framework.GameTestServer; +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; + +@Mixin(GameTestServer.class) +public abstract class MixinGameTestServer implements IEDGMinecraftServer { + @WrapOperation( + method = "initServer", + at = @At( + value = "HEAD", + target = "Lnet/minecraft/gametest/framework/GameTestServer;setPlayerList(Lnet/minecraft/server/players/PlayerList;)V" + ) + ) + void edg$initServer$setNPCList(GameTestServer instance, PlayerList playerList, Operation original) { + original.call(instance, playerList); + this.setNPCPlayerList(new NPCPlayerList(instance, instance.registries(), this.getNPCDataStorage())); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinIntegratedServer.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinIntegratedServer.java new file mode 100644 index 00000000..8c9cc5e2 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinIntegratedServer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.mixin.minecraft; + +import com.google.common.collect.Lists; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer; +import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; + +import javax.annotation.Nullable; +import java.util.UUID; + +@Mixin(IntegratedServer.class) +public abstract class MixinIntegratedServer implements IEDGMinecraftServer { + @Shadow @Nullable private UUID uuid; + + @Inject( + method = "", + at = @At("TAIL") + ) + private void edg$init(CallbackInfo ci) { + IntegratedServer server = IntegratedServer.class.cast(this); + setNPCPlayerList(new NPCPlayerList(server, server.registries(), getNPCDataStorage())); + } + @WrapOperation( + method = "halt", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/server/IntegratedServer;executeBlocking(Ljava/lang/Runnable;)V" + ) + ) + private void edg$halt$3$removeNpc(IntegratedServer instance, Runnable runnable, @NotNull Operation original) { + original.call(instance, runnable); + original.call(instance, (Runnable) () -> { + for(NPCServerPlayer npcServerPlayer : Lists.newArrayList(getNPCPlayerList().getPlayers())) { + if (!npcServerPlayer.getUUID().equals(this.uuid)) { + getNPCPlayerList().remove(npcServerPlayer); + } + } + }); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinLevelStorageSource$LevelStorageAccess.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinLevelStorageSource$LevelStorageAccess.java new file mode 100644 index 00000000..4736df42 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinLevelStorageSource$LevelStorageAccess.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.mixin.minecraft; + +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage; +import top.r3944realms.eroticdungeongame.util.IEDGLevelStorageAccess; + +@Mixin(LevelStorageSource.LevelStorageAccess.class) +public abstract class MixinLevelStorageSource$LevelStorageAccess implements IEDGLevelStorageAccess { + + @Shadow protected abstract void checkLock(); + + @SuppressWarnings("AddedMixinMembersNamePattern") + @Override + public NPCDataStorage createNPCPlayerDataStorage() { + this.checkLock(); + return new NPCDataStorage(LevelStorageSource.LevelStorageAccess.class.cast(this), DataFixers.getDataFixer()); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinMinecraftServer.java b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinMinecraftServer.java index 1fd541a9..b95ba9b1 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinMinecraftServer.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/mixin/minecraft/MixinMinecraftServer.java @@ -18,24 +18,43 @@ package top.r3944realms.eroticdungeongame.mixin.minecraft; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.core.RegistryAccess; -import net.minecraft.nbt.CompoundTag; +import com.mojang.datafixers.DataFixer; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.Services; +import net.minecraft.server.WorldStem; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.players.PlayerList; import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.WorldData; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage; +import top.r3944realms.eroticdungeongame.util.IEDGLevelStorageAccess; import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer; +import java.net.Proxy; + @Mixin(MinecraftServer.class) public class MixinMinecraftServer implements IEDGMinecraftServer { + @Unique + private NPCDataStorage edg$npcDataStorage; @Unique private NPCPlayerList edg$npcPlayerList; + @Inject( + method = "", + at = @At("TAIL") + ) + private void edg$init(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory, CallbackInfo ci) { + if (storageSource instanceof IEDGLevelStorageAccess iedgLevelStorageAccess) { + edg$npcDataStorage = iedgLevelStorageAccess.createNPCPlayerDataStorage(); + } + } @SuppressWarnings("AddedMixinMembersNamePattern") @Override @@ -48,6 +67,13 @@ public class MixinMinecraftServer implements IEDGMinecraftServer { public void setNPCPlayerList(NPCPlayerList playerList) { this.edg$npcPlayerList = playerList; } + + @SuppressWarnings("AddedMixinMembersNamePattern") + @Override + public NPCDataStorage getNPCDataStorage() { + return edg$npcDataStorage; + } + @WrapOperation( method = "saveEverything", at = @At( @@ -84,5 +110,17 @@ public class MixinMinecraftServer implements IEDGMinecraftServer { edg$npcPlayerList.saveAll(); edg$npcPlayerList.removeAll(); } + @WrapOperation( + method = "tickChildren", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/players/PlayerList;tick()V" + ) + ) + void edg$tickChildren$tick(PlayerList instance, @NotNull Operation original) { + original.call(instance); + edg$npcPlayerList.tick(); + } + } diff --git a/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGEntitySelector.java b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGEntitySelector.java new file mode 100644 index 00000000..8cd1d30f --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGEntitySelector.java @@ -0,0 +1,20 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.util; + +public interface IEDGEntitySelector { +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGLevelStorageAccess.java b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGLevelStorageAccess.java new file mode 100644 index 00000000..7f58b5ce --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGLevelStorageAccess.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-2026 R3944Realms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.r3944realms.eroticdungeongame.util; + +import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage; + +public interface IEDGLevelStorageAccess { + NPCDataStorage createNPCPlayerDataStorage(); +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGMinecraftServer.java b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGMinecraftServer.java index 96bc1bf2..b8305ef5 100644 --- a/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGMinecraftServer.java +++ b/src/main/java/top/r3944realms/eroticdungeongame/util/IEDGMinecraftServer.java @@ -17,8 +17,10 @@ package top.r3944realms.eroticdungeongame.util; import top.r3944realms.eroticdungeongame.content.entity.npc.NPCPlayerList; +import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage; public interface IEDGMinecraftServer { NPCPlayerList getNPCPlayerList(); void setNPCPlayerList(NPCPlayerList playerList); + NPCDataStorage getNPCDataStorage(); } diff --git a/src/main/resources/eroticdungeongame.mixins.json b/src/main/resources/eroticdungeongame.mixins.json index ccad17c9..9caac11a 100644 --- a/src/main/resources/eroticdungeongame.mixins.json +++ b/src/main/resources/eroticdungeongame.mixins.json @@ -8,11 +8,14 @@ "mixins": [ "bendylib.MixinBendableCuboidBuilder", "minecraft.MixinClientConnect", + "minecraft.MixinDedicatedServer", "minecraft.MixinEntity", + "minecraft.MixinEntitySelector", + "minecraft.MixinGameTestServer", + "minecraft.MixinLevelStorageSource$LevelStorageAccess", "minecraft.MixinLivingEntity", "minecraft.MixinMinecraftServer", "minecraft.MixinPlayer", - "minecraft.MixinServerLoginPacketListenerImpl", "minecraft.MixinServerPlayer" ], "client": [ @@ -20,6 +23,7 @@ "minecraft.MixinClientPacketListener", "minecraft.MixinEntityRenderer", "minecraft.MixinGameRender", + "minecraft.MixinIntegratedServer", "minecraft.MixinItemInHandRenderer", "minecraft.MixinMinecraft", "minecraft.MixinPlayerRenderer" diff --git a/src/main/templates/META-INF/accesstransformer.cfg b/src/main/templates/META-INF/accesstransformer.cfg index 25228c67..a4387ca3 100644 --- a/src/main/templates/META-INF/accesstransformer.cfg +++ b/src/main/templates/META-INF/accesstransformer.cfg @@ -45,4 +45,5 @@ public net.minecraft.client.multiplayer.ClientPacketListener m_245842_(Lnet/mine public net.minecraft.client.multiplayer.ClientPacketListener f_244156_ # listedPlayers public net.minecraft.client.multiplayer.PlayerInfo m_105317_(Lnet/minecraft/world/level/GameType;)V # setGameMode public net.minecraft.client.multiplayer.PlayerInfo m_105313_(I)V # setLatency -public net.minecraft.client.multiplayer.ClientPacketListener f_104892_ # playerInfoMap \ No newline at end of file +public net.minecraft.client.multiplayer.ClientPacketListener f_104892_ # playerInfoMap +public-f net.minecraft.network.protocol.game.ClientboundAddPlayerPacket f_131588_ # playerId \ No newline at end of file