fix: 修复已存在的NPC在登陆后看不见、状态不对的问题

This commit is contained in:
叁玖领域 2026-03-23 02:00:21 +08:00
parent 872c69f4fe
commit 4ddcb53ea0
19 changed files with 783 additions and 48 deletions

View File

@ -0,0 +1,353 @@
/*
* 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.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.content.entity.npc.INPCPlayer;
import top.r3944realms.eroticdungeongame.util.IEDGClientPacketListener;
import top.r3944realms.superleadrope.util.riding.RidingFinder;
import java.util.*;
@SuppressWarnings("ConstantValue")
public class ClientNPCManager {
// 缓存待处理的 NPC 玩家包
private static final Set<ClientboundAddPlayerPacket> pendingNPCs = new HashSet<>();
// 缓存待处理的实体包坐骑宠物等
// private static final Set<ClientboundAddEntityPacket> pendingEntities = new HashSet<>();
// 缓存待处理的实体数据包
private static final Set<ClientboundSetEntityDataPacket> pendingEntityData = new HashSet<>();
// 缓存待处理的属性包
private static final Set<ClientboundUpdateAttributesPacket> pendingAttributes = new HashSet<>();
// 缓存待处理的装备包
private static final Set<ClientboundSetEquipmentPacket> pendingEquipment = new HashSet<>();
// 缓存待处理的乘客包
private static final Set<ClientboundSetPassengersPacket> pendingPassengers = new HashSet<>();
// 缓存待处理的链接包拴绳
private static final Set<ClientboundSetEntityLinkPacket> pendingLinks = new HashSet<>();
// 缓存待处理的头部旋转包
private static final Set<ClientboundRotateHeadPacket> pendingHeadRotations = new HashSet<>();
// 缓存待处理的移动包
private static final Set<Packet<?>> pendingMovements = new HashSet<>();
// 缓存待处理的运动包
private static final Set<ClientboundSetEntityMotionPacket> pendingMotions = new HashSet<>();
public static void reset() {
pendingNPCs.clear();
// pendingEntities.clear();
pendingEntityData.clear();
pendingAttributes.clear();
pendingEquipment.clear();
pendingPassengers.clear();
pendingLinks.clear();
pendingHeadRotations.clear();
pendingMovements.clear();
pendingMotions.clear();
EroticDungeon.LOGGER.debug("ClientNPCManager reset");
}
// ========== 缓存方法 ==========
/**
* 缓存 NPC 玩家包
*/
public static void cacheNPCIfNeeded(ClientboundAddPlayerPacket packet) {
ClientPacketListener connection = Minecraft.getInstance().getConnection();
if (connection instanceof IEDGClientPacketListener iedgListener) {
UUID playerId = packet.getPlayerId();
PlayerInfo info = iedgListener.getNPCPlayerInfoMap().get(playerId);
if (info == null && !iedgListener.getNPCPlayerInfoMap().containsKey(playerId)) {
pendingNPCs.add(packet);
EroticDungeon.LOGGER.debug("Cached AddPlayer packet for NPC: {} (entityId: {})",
playerId, packet.getEntityId());
}
}
}
// /**
// * 缓存实体包
// */
// public static void cacheEntityIfNeeded(@NotNull ClientboundAddEntityPacket packet) {
// pendingEntities.add(packet);
// EroticDungeon.LOGGER.debug("Cached AddEntity packet {}", packet);
// }
/**
* 缓存实体数据包
*/
public static void cacheEntityDataIfNeeded(@NotNull ClientboundSetEntityDataPacket packet) {
pendingEntityData.add(packet);
EroticDungeon.LOGGER.debug("Cached SetEntityData packet {}, ID: {}", packet, packet.id());
}
/**
* 缓存属性包
*/
public static void cacheAttributesIfNeeded(@NotNull ClientboundUpdateAttributesPacket packet) {
pendingAttributes.add(packet);
EroticDungeon.LOGGER.debug("Cached UpdateAttributes packet {}, ID: {}", packet, packet.getEntityId());
}
/**
* 缓存装备包
*/
public static void cacheEquipmentIfNeeded(@NotNull ClientboundSetEquipmentPacket packet) {
pendingEquipment.add(packet);
EroticDungeon.LOGGER.debug("Cached SetEquipment packet{}, ID: {}", packet, packet.getEntity());
}
/**
* 缓存乘客包
*/
public static void cachePassengersIfNeeded(@NotNull ClientboundSetPassengersPacket packet) {
pendingPassengers.add(packet);
EroticDungeon.LOGGER.debug("Cached SetPassengers packet {}, (as vehicle) ID: {}", packet, packet.getVehicle());
}
/**
* 缓存链接包拴绳
*/
public static void cacheLinkIfNeeded(@NotNull ClientboundSetEntityLinkPacket packet) {
pendingLinks.add(packet);
EroticDungeon.LOGGER.debug("Cached SetEntityLink packet {}, (as source) ID: {}", packet, packet.getSourceId());
}
/**
* 缓存头部旋转包
*/
public static void cacheHeadRotationIfNeeded(ClientboundRotateHeadPacket packet) {
pendingHeadRotations.add(packet);
EroticDungeon.LOGGER.debug("Cached RotateHead packet {}", packet);
}
/**
* 缓存移动包
*/
public static void cacheMoveEntityIfNeeded(ClientboundMoveEntityPacket packet) {
pendingMovements.add(packet);
EroticDungeon.LOGGER.debug("Cached MoveEntity packet {}", packet);
}
/**
* 缓存传送包
*/
public static void cacheTeleportEntityIfNeeded(ClientboundTeleportEntityPacket packet) {
EroticDungeon.LOGGER.debug("Cached TeleportEntity packet {}", packet);
pendingMovements.add(packet);
}
/**
* 缓存运动包
*/
public static void cacheMotionIfNeeded(ClientboundSetEntityMotionPacket packet) {
pendingMotions.add(packet);
EroticDungeon.LOGGER.debug("Cached SetEntityMotion packet {}, ID: {}", packet, packet.getId());
}
// ========== 处理方法 ==========
/**
* 处理缓存的 NPC NPCInfo 到达时调用
*/
public static void processPendingNPCs(@NotNull PlayerInfo npcInfo) {
UUID uuid = npcInfo.getProfile().getId();
ClientPacketListener connection = Minecraft.getInstance().getConnection();
if (!(connection instanceof IEDGClientPacketListener iedgListener)) {
return;
}
// 处理缓存的 AddPlayer
Iterator<ClientboundAddPlayerPacket> playerIterator = pendingNPCs.iterator();
while (playerIterator.hasNext()) {
ClientboundAddPlayerPacket packet = playerIterator.next();
if (packet.getPlayerId().equals(uuid)) {
PlayerInfo npcPlayerInfo = iedgListener.getNPCPlayerInfo(uuid);
if (npcPlayerInfo != null) {
// 创建 NPC 玩家实体
iedgListener.edg$createNPCPlayer(packet, npcPlayerInfo);
playerIterator.remove();
EroticDungeon.LOGGER.debug("Processed AddPlayer packet for NPC: {}", uuid);
// 处理所有缓存的包
processAllCachedPackets(iedgListener);
break;
} else {
EroticDungeon.LOGGER.warn("NPC Player {} not found", uuid);
}
}
}
}
/**
* 处理所有缓存的包
*/
private static void processAllCachedPackets(IEDGClientPacketListener iedgListener) {
Level level = Minecraft.getInstance().level;
if (level == null) return;
// // 处理实体包
// Iterator<ClientboundAddEntityPacket> entityIterator = pendingEntities.iterator();
// while (entityIterator.hasNext()) {
// ClientboundAddEntityPacket packet = entityIterator.next();
// Entity entity = level.getEntity(packet.getId());
// if (entity != null) {
// iedgListener.edg$createEntity(packet);
// entityIterator.remove();
// EroticDungeon.LOGGER.debug("Processed AddEntity packet: {}", entity.getUUID());
// }
//
// }
// 处理实体数据包
Iterator<ClientboundSetEntityDataPacket> dataIterator = pendingEntityData.iterator();
while (dataIterator.hasNext()) {
ClientboundSetEntityDataPacket packet = dataIterator.next();
Entity entity = level.getEntity(packet.id());
if (entity != null) {
iedgListener.edg$setEntityData(packet);
dataIterator.remove();
EroticDungeon.LOGGER.debug("Processed SetEntityData packet: {}", entity.getUUID());
}
}
// 处理属性包
Iterator<ClientboundUpdateAttributesPacket> attrIterator = pendingAttributes.iterator();
while (attrIterator.hasNext()) {
ClientboundUpdateAttributesPacket packet = attrIterator.next();
Entity entity = level.getEntity(packet.getEntityId());
if (entity != null) {
iedgListener.edg$updateAttributes(packet);
attrIterator.remove();
EroticDungeon.LOGGER.debug("Processed UpdateAttributes packet: {}", entity.getUUID());
}
}
// 处理装备包
Iterator<ClientboundSetEquipmentPacket> equipIterator = pendingEquipment.iterator();
while (equipIterator.hasNext()) {
ClientboundSetEquipmentPacket packet = equipIterator.next();
Entity entity = level.getEntity(packet.getEntity());
if (entity != null) {
iedgListener.edg$setEquipment(packet);
equipIterator.remove();
EroticDungeon.LOGGER.debug("Processed SetEquipment packet for: {}", entity.getUUID());
}
}
// 处理乘客包
Iterator<ClientboundSetPassengersPacket> passengerIterator = pendingPassengers.iterator();
while (passengerIterator.hasNext()) {
ClientboundSetPassengersPacket packet = passengerIterator.next();
Entity vehicle = level.getEntity(packet.getVehicle());
if (vehicle != null) {
boolean flag = true;
for (int passengerId : packet.getPassengers()) {
Entity passenger = level.getEntity(passengerId);
if (passenger == null) {
flag = false;
break;
}
}
if (flag) {
iedgListener.edg$setPassengers(packet);
passengerIterator.remove();
EroticDungeon.LOGGER.debug("Processed SetPassengers packet: {}", vehicle.getUUID());
}
}
}
// 处理链接包
Iterator<ClientboundSetEntityLinkPacket> linkIterator = pendingLinks.iterator();
while (linkIterator.hasNext()) {
ClientboundSetEntityLinkPacket packet = linkIterator.next();
Entity source = level.getEntity(packet.getSourceId());
Entity dest = level.getEntity(packet.getDestId());
if (source != null && dest != null) {
iedgListener.edg$setEntityLink(packet);
linkIterator.remove();
EroticDungeon.LOGGER.debug("Processed SetEntityLink packet: {}", source.getUUID());
}
}
// 处理头部旋转包
Iterator<ClientboundRotateHeadPacket> headIterator = pendingHeadRotations.iterator();
while (headIterator.hasNext()) {
ClientboundRotateHeadPacket packet = headIterator.next();
Entity entity = packet.getEntity(level);
if (entity != null){
iedgListener.edg$rotateHead(packet);
headIterator.remove();
}
EroticDungeon.LOGGER.debug("Processed RotateHead packet for NPC: {}", entity.getUUID());
}
// 处理移动包
Iterator<Packet<?>> moveIterator = pendingMovements.iterator();
while (moveIterator.hasNext()) {
Packet<?> packet = moveIterator.next();
Entity entity = null;
if (packet instanceof ClientboundMoveEntityPacket movePacket) {
entity = movePacket.getEntity(level);
} else if (packet instanceof ClientboundTeleportEntityPacket teleportPacket) {
entity = level.getEntity(teleportPacket.getId());
}
if (entity != null) {
if (packet instanceof ClientboundMoveEntityPacket movePacket) {
iedgListener.edg$moveEntity(movePacket);
} else if (packet instanceof ClientboundTeleportEntityPacket teleportPacket) {
iedgListener.edg$teleportEntity(teleportPacket);
}
moveIterator.remove();
EroticDungeon.LOGGER.debug("Processed movement packet for NPC: {}", entity.getUUID());
}
}
// 处理运动包
Iterator<ClientboundSetEntityMotionPacket> motionIterator = pendingMotions.iterator();
while (motionIterator.hasNext()) {
ClientboundSetEntityMotionPacket packet = motionIterator.next();
Entity entity = level.getEntity(packet.getId());
if (entity != null) {
iedgListener.edg$setEntityMotion(packet);
motionIterator.remove();
EroticDungeon.LOGGER.debug("Processed SetEntityMotion packet for NPC: {}", entity.getUUID());
}
}
}
}

View File

@ -19,7 +19,9 @@ package top.r3944realms.eroticdungeongame.content.animation;
import io.zershyan.sccore.animation.AnimationApi;
import io.zershyan.sccore.animation.helper.AnimationHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import org.apache.logging.log4j.core.jmx.Server;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.EroticDungeon;
@ -50,14 +52,14 @@ public interface IEDGAnimation {
default void play(Player player) {
if (player != null && !player.level().isClientSide) {
if (player != null) {
AnimationHelper helper = AnimationApi.getHelper(player);
helper.playAnimation(IEDGAnimation.LAYER, getBaseId().withSuffix("01"));
}
}
default void play(Player player, int varNumber) {
if (player != null && !player.level().isClientSide) {
if (player != null) {
AnimationHelper helper = AnimationApi.getHelper(player);
helper.playAnimation(IEDGAnimation.LAYER, getBaseId().withSuffix(String.format("%02d", Math.max(1, Math.min(99, varNumber)))));
}
@ -65,7 +67,7 @@ public interface IEDGAnimation {
@Nullable
static ResourceLocation getPlayingAnimation(Player player) {
if (player != null && !player.level().isClientSide) {
if (player != null) {
AnimationHelper helper = AnimationApi.getHelper(player);
return helper.getAnimationPlaying(IEDGAnimation.LAYER);
}
@ -81,9 +83,16 @@ public interface IEDGAnimation {
}
static void stop(Player player) {
if (player != null && !player.level().isClientSide) {
if (player != null) {
AnimationHelper helper = AnimationApi.getHelper(player);
helper.removeAnimation(IEDGAnimation.LAYER);
}
}
static void refresh(ServerPlayer target, Player receiver) {
if (target != null) {
AnimationHelper helper = AnimationApi.getHelper(target);
helper.playAnimation(LAYER, helper.getAnimationPlaying(IEDGAnimation.LAYER));
}
}
}

View File

@ -93,8 +93,8 @@ public abstract class BaseSeatBlockEntity extends BlockEntity {
return false;
}
protected final void playAnimation(Player player, ISeatType ISeatType) {
if(getPlayerAnimationNumber() == 1) {
public final void playAnimation(Player player, ISeatType ISeatType) {
if (getPlayerAnimationNumber() == 1) {
SeatService.playBindingAnimation(player, ISeatType);
} else SeatService.playBindingAnimation(player, ISeatType, getPlayerAnimationNumber());
}

View File

@ -64,7 +64,6 @@ public class SeatEntity extends Entity {
if (!this.level().isClientSide) {
// 简化的检查逻辑
if (shouldRemove() && linkedBlockPos != null) {
SeatService.releasePlayerFromBlock(this.level(), this.linkedBlockPos);
this.discard();
}
} else {
@ -234,7 +233,7 @@ public class SeatEntity extends Entity {
@Override
public void remove(@NotNull RemovalReason reason) {
if (!level().isClientSide && this.linkedBlockPos != null) {
SeatService.releasePlayerFromBlock(level(), this.linkedBlockPos);
SeatService.releasePlayerFromBlock(level(), this.linkedBlockPos, this);
}
super.remove(reason);
}

View File

@ -47,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.Stats;
import net.minecraft.tags.TagNetworkSerialization;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
@ -139,7 +138,6 @@ public class NPCPlayerList {
servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()));
servergamepacketlistenerimpl.send(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
this.sendPlayerPermissionLevel(npc);
npc.getStats().markAllDirty();
npc.getRecipeBook().sendInitialRecipeBook(npc);
this.updateEntireScoreboard(serverlevel1.getScoreboard(), npc);
this.server.invalidateStatus();
@ -364,11 +362,11 @@ public class NPCPlayerList {
protected void save(NPCServerPlayer npcServerPlayer) {
this.npcIo.save(npcServerPlayer);
}
public void removeAll() {
for (NPCServerPlayer npc : this.npcs) {
npc.connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
}
}
public void delete(NPCServerPlayer npc) {
@ -379,15 +377,14 @@ public class NPCPlayerList {
public void remove(NPCServerPlayer npc) {
net.minecraftforge.event.ForgeEventFactory.firePlayerLoggedOut(npc);
ServerLevel serverlevel = npc.serverLevel();
npc.awardStat(Stats.LEAVE_GAME);
this.save(npc);
if (npc.isPassenger()) {
Entity entity = npc.getRootVehicle();
if (entity.hasExactlyOnePlayerPassenger()) {
LOGGER.debug("Removing player mount");
npc.stopRiding();
entity.getPassengersAndSelf().forEach((p_215620_) -> {
p_215620_.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
entity.getPassengersAndSelf().forEach((entity1) -> {
entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
});
}
}
@ -508,6 +505,7 @@ public class NPCPlayerList {
}
}
public MinecraftServer getServer() {
return this.server;
}

View File

@ -68,6 +68,7 @@ public class NPCServerPlayer extends ServerPlayer implements INPCPlayer {
};
public NPCServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile) {
super(server, level, gameProfile);
getAdvancements().stopListening();
}
public static @Nullable NPCServerPlayer createNPC(String username, @NotNull MinecraftServer server, double x, double y, double z, double yaw, double pitch, ResourceKey<Level> dimensionId, GameType gamemode, boolean isflying) {

View File

@ -27,6 +27,9 @@ import top.r3944realms.eroticdungeongame.content.animation.IEDGAnimation;
import top.r3944realms.eroticdungeongame.content.block.ISeatBlock;
import top.r3944realms.eroticdungeongame.content.block.blockentity.BaseSeatBlockEntity;
import top.r3944realms.eroticdungeongame.content.util.FurnitureHelper;
import top.r3944realms.eroticdungeongame.core.device.ISeatType;
import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler;
import top.r3944realms.eroticdungeongame.core.network.toServer.RefreshPlayerAnimationPacket;
import top.r3944realms.eroticdungeongame.util.IEDGEntity;
import top.r3944realms.lib39.core.sync.CachedSyncManager;
@ -39,12 +42,15 @@ public class DungeonDataSyncManager extends CachedSyncManager<Capability<Abstrac
public Map<Capability<AbstractPlayerDungeonData>, AbstractPlayerDungeonData> getSyncMap() {
return playerDungeonData;
}
static int cooldown = 0;
public static void tick(@NotNull Player player) {
if (cooldown > 0) {
cooldown--;
}
player.getCapability(PlayerDungeonDataProvider.PLAYER_DUNGEON_DATA_CAP).ifPresent(
cap -> {
if (player.level().isClientSide) {
clientTick(player);
clientTick(player, cap);
} else if (cap.getDeviceMainBlockPos() != null) {
if (cap.getEyeHeight() != -1.0 && cap.getEyeHeight() >= 0.0) {
EntityDimensions dimensions = player.getDimensions(player.getPose());
@ -57,8 +63,16 @@ public class DungeonDataSyncManager extends CachedSyncManager<Capability<Abstrac
}
@OnlyIn(Dist.CLIENT)
public static void clientTick(@NotNull Player player) {
public static void clientTick(@NotNull Player player, AbstractPlayerDungeonData dungeonData) {
if (IEDGAnimation.getPlayingAnimation(player) == null) {
if (dungeonData.getDeviceMainBlockPos() != null && player.level().getBlockEntity(dungeonData.getDeviceMainBlockPos()) instanceof BaseSeatBlockEntity baseSeatBlockEntity) {
ISeatType seatType = FurnitureHelper.getSeatType(baseSeatBlockEntity.getBlockState().getBlock());
if (seatType != null && cooldown == 0) {
EDGNetworkHandler.sendToServer(new RefreshPlayerAnimationPacket(player.getUUID()));
cooldown = 100; //5s
}
}
}
}
public static void login(@NotNull Player player) {

View File

@ -41,6 +41,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.api.workspace.Services;
import top.r3944realms.eroticdungeongame.client.ClientNPCManager;
import top.r3944realms.eroticdungeongame.client.EDGKeyBindings;
import top.r3944realms.eroticdungeongame.client.gui.screens.DungeonCraftingScreen;
import top.r3944realms.eroticdungeongame.client.model.EDGArmPose;
@ -58,6 +59,7 @@ import top.r3944realms.eroticdungeongame.content.item.DeviceKeyItem;
import top.r3944realms.eroticdungeongame.content.particle.VerticalWhipSweepParticle;
import top.r3944realms.eroticdungeongame.content.particle.WhipScarParticle;
import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler;
import top.r3944realms.eroticdungeongame.core.network.NPCEmptyClientConnection;
import top.r3944realms.eroticdungeongame.core.network.toServer.RequestQuitDevicePacket;
import top.r3944realms.eroticdungeongame.core.register.EDGBlockEntities;
import top.r3944realms.eroticdungeongame.core.register.EDGEntities;
@ -74,6 +76,19 @@ import java.util.Optional;
public class ClientHandler {
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = EroticDungeon.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT)
public static class Game extends ClientHandler {
@SubscribeEvent
public static void onLoginIn(ClientPlayerNetworkEvent.LoggingIn event) {
if (!(event.getConnection() instanceof NPCEmptyClientConnection)) {
ClientNPCManager.reset();
}
}
@SubscribeEvent
public static void onLoginOut(ClientPlayerNetworkEvent.LoggingOut event) {
if (!(event.getConnection() instanceof NPCEmptyClientConnection)) {
ClientNPCManager.reset();
}
}
@SubscribeEvent
public static void onSpanishDonkeyTremble(RenderPlayerEvent event) {

View File

@ -32,6 +32,7 @@ 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.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
@ -78,6 +79,10 @@ public class CommonHandler {
iedgMinecraftServer.getNPCPlayerList().loadAllNPCs();
}
}
@SubscribeEvent
public static void onServerStopping(@NotNull ServerStoppingEvent event) {
}
@SubscribeEvent
public static void syncCapabilities(SyncManagerRegisterEvent event) {
@ -85,6 +90,7 @@ public class CommonHandler {
event.registerSyncManager(DUNGEON_SYNC, dungeonDataSyncManager, PlayerDungeonDataProvider.PLAYER_DUNGEON_DATA_CAP);
event.addAllowEntityClass(DUNGEON_SYNC, Player.class);
}
private static char ticks = 0;
@SubscribeEvent
public static void onPlayerTick (TickEvent.PlayerTickEvent event) {
@ -103,7 +109,6 @@ public class CommonHandler {
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();
}
}

View File

@ -28,6 +28,7 @@ import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.core.network.toClient.NPCInfoRemovePacket;
import top.r3944realms.eroticdungeongame.core.network.toClient.NPCInfoUpdatePacket;
import top.r3944realms.eroticdungeongame.core.network.toServer.LoveMachineConfigPacket;
import top.r3944realms.eroticdungeongame.core.network.toServer.RefreshPlayerAnimationPacket;
import top.r3944realms.eroticdungeongame.core.network.toServer.RequestQuitDevicePacket;
import top.r3944realms.eroticdungeongame.util.IEDGMinecraftServer;
@ -64,11 +65,16 @@ public class EDGNetworkHandler {
.decoder(NPCInfoRemovePacket::new)
.consumerMainThread(NPCInfoRemovePacket::handle)
.add();
CHANNEL.messageBuilder(NPCInfoUpdatePacket.class, getIndex(), NetworkDirection.PLAY_TO_CLIENT)
CHANNEL.messageBuilder(NPCInfoUpdatePacket.class, getIndex())
.encoder(NPCInfoUpdatePacket::write)
.decoder(NPCInfoUpdatePacket::new)
.consumerMainThread(NPCInfoUpdatePacket::handle)
.add();
CHANNEL.messageBuilder(RefreshPlayerAnimationPacket.class, getIndex(), NetworkDirection.PLAY_TO_SERVER)
.encoder(RefreshPlayerAnimationPacket::encode)
.decoder(RefreshPlayerAnimationPacket::new)
.consumerMainThread(RefreshPlayerAnimationPacket::handle)
.add();
}
public static int getIndex() {
return cid.getAndIncrement();
@ -81,4 +87,7 @@ public class EDGNetworkHandler {
public static <MSG> void sendToAllPlayer(MSG message){
CHANNEL.send(ALL_PLAYER.noArg(), message);
}
public static <MSG> void sendToServer(MSG message){
CHANNEL.sendToServer(message);
}
}

View File

@ -33,6 +33,7 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.client.ClientNPCManager;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
import top.r3944realms.eroticdungeongame.util.IEDGClientPacketListener;
@ -96,6 +97,7 @@ public final class NPCInfoUpdatePacket {
for (Entry entry : packet.newEntries()) {
PlayerInfo npcInfo = new PlayerInfo(entry.profile, false);
iedgClientPacketListener.getNPCPlayerInfoMap().putIfAbsent(entry.uuid(), npcInfo);
ClientNPCManager.processPendingNPCs(npcInfo);
}
for (Entry entry : packet.entries) {
PlayerInfo npcInfo = iedgClientPacketListener.getNPCPlayerInfoMap().get(entry.uuid);

View File

@ -0,0 +1,48 @@
/*
* Copyright 2025-2026 R3944Realms
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.r3944realms.eroticdungeongame.core.network.toServer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.eroticdungeongame.content.animation.IEDGAnimation;
import java.util.UUID;
import java.util.function.Supplier;
public record RefreshPlayerAnimationPacket(UUID uuid) {
public RefreshPlayerAnimationPacket(@NotNull FriendlyByteBuf buf) {
this(buf.readUUID());
}
public void encode(@NotNull FriendlyByteBuf buf) {
buf.writeUUID(uuid);
}
public void handle(@NotNull Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
ServerPlayer sender = context.getSender();
context.enqueueWork(() -> {
if (sender != null) {
if (sender.serverLevel().getEntity(uuid) instanceof ServerPlayer target) {
IEDGAnimation.refresh(target, sender);
}
}
});
context.setPacketHandled(true);
}
}

View File

@ -28,6 +28,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.api.event.RideDeviceEvent;
import top.r3944realms.eroticdungeongame.api.event.UnRideDeviceEvent;
@ -125,6 +126,17 @@ public class SeatService {
return false;
}
/**
* 从方块位置释放玩家(但会检查是否为目标座椅避免释放错座椅)
*/
public static boolean releasePlayerFromBlock(@NotNull Level level, @NotNull BlockPos blockPos, SeatEntity seat) {
BaseSeatBlockEntity blockEntity = FurnitureHelper.getSeatBlockEntity(level, blockPos);
if (blockEntity != null) {
return releasePlayer(level, blockPos, seat);
}
return false;
}
public static @NotNull Optional<SeatEntity> getPlayerSeat(@NotNull Player player) {
return player.getCapability(PlayerDungeonDataProvider.PLAYER_DUNGEON_DATA_CAP).resolve().map(cap -> {
BlockPos deviceMainBlockPos = cap.getDeviceMainBlockPos();
@ -241,14 +253,29 @@ public class SeatService {
*/
public static boolean releasePlayer(@NotNull Level level, BlockPos blockPos) {
BaseSeatBlockEntity seatBlockEntity = FurnitureHelper.getSeatBlockEntity(level, blockPos);
return releasePlayerInternal(seatBlockEntity, level, blockPos);
BlockState blockState = level.getBlockState(blockPos);
boolean flag = releasePlayerInternal(seatBlockEntity, level, blockState, blockPos);
if (flag) {
FurnitureHelper.setSeatOccupied(level, blockPos, false);
}
return flag;
}
public static boolean releasePlayer(@NotNull Level level, BlockPos blockPos,@NotNull SeatEntity seat) {
BaseSeatBlockEntity seatBe = FurnitureHelper.getSeatBlockEntity(level, blockPos);
BlockState blockState = level.getBlockState(blockPos);
boolean flag = releasePlayerInternal(seatBe, level, blockPos, blockState, seat);
if (flag) {
FurnitureHelper.setSeatOccupied(level, blockPos, false);
}
return flag;
}
private static boolean releasePlayerInternal(BaseSeatBlockEntity be, @NotNull Level level, BlockPos blockPos) {
BlockState blockState = level.getBlockState(blockPos);
// 1. 更新方块状态
FurnitureHelper.setSeatOccupied(level, blockPos, false);
private static boolean releasePlayerInternal(BaseSeatBlockEntity be, @NotNull Level level, BlockState blockState, BlockPos blockPos) {
return releasePlayerInternal(be, level, blockPos, blockState, null);
}
//检查是否与释放预期座椅一致
private static boolean releasePlayerInternal(BaseSeatBlockEntity be, @NotNull Level level, BlockPos blockPos, BlockState blockState, @Nullable SeatEntity seat) {
// 2. 处理实体只在服务端
if (level instanceof ServerLevel) {
Player playerByUUID;
@ -256,13 +283,16 @@ public class SeatService {
if (be.getBoundPlayerUUID() != null) {
playerByUUID = level.getPlayerByUUID(be.getBoundPlayerUUID());
if (playerByUUID != null) {
SeatEntity seat = null;
SeatEntity seat_ = null;
if (playerByUUID.isPassenger() && playerByUUID.getVehicle() instanceof SeatEntity seatEntity) {
seat = seatEntity;
seat_ = seatEntity;
if (seat != null && seat_ != seat) {
return false; //预期座椅不一致
}
}
isCancelled = EroticDungeon.EVENT_BUS.post(new UnRideDeviceEvent.Pre(playerByUUID, blockPos, blockState, be, seat));
isCancelled = EroticDungeon.EVENT_BUS.post(new UnRideDeviceEvent.Pre(playerByUUID, blockPos, blockState, be, seat_));
if (!isCancelled) {
if(seat != null) ((IEDGEntity)(playerByUUID)).releaseEntityFromEDGDevice(true);
if(seat_ != null) ((IEDGEntity)(playerByUUID)).releaseEntityFromEDGDevice(true);
playerByUUID.getCapability(PlayerDungeonDataProvider.PLAYER_DUNGEON_DATA_CAP)
.ifPresent(i -> i.clearDungeonData(playerByUUID));
} else {

View File

@ -1,3 +1,19 @@
/*
* 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.datagen;
import net.minecraft.core.RegistrySetBuilder;

View File

@ -20,16 +20,18 @@ 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 com.mojang.brigadier.ParseResults;
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.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.PacketUtils;
import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.vehicle.Boat;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -40,6 +42,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import top.r3944realms.eroticdungeongame.client.ClientNPCManager;
import top.r3944realms.eroticdungeongame.client.EDGKeyBindings;
import top.r3944realms.eroticdungeongame.content.entity.SeatEntity;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCRemotePlayer;
@ -50,6 +53,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
@SuppressWarnings("ConstantValue")
@Mixin(ClientPacketListener.class)
public abstract class MixinClientPacketListener implements IEDGClientPacketListener {
@Unique
@ -66,6 +70,27 @@ public abstract class MixinClientPacketListener implements IEDGClientPacketListe
@Shadow @Final private Set<PlayerInfo> listedPlayers;
@Shadow private ClientLevel level;
@Shadow public abstract void handleSetEntityMotion(ClientboundSetEntityMotionPacket packet);
@Shadow public abstract void handleSetEntityData(ClientboundSetEntityDataPacket packet);
@Shadow public abstract void handleUpdateAttributes(ClientboundUpdateAttributesPacket packet);
@Shadow public abstract void handleSetEquipment(ClientboundSetEquipmentPacket packet);
@Shadow public abstract void handleSetEntityPassengersPacket(ClientboundSetPassengersPacket packet);
@Shadow public abstract void handleEntityLinkPacket(ClientboundSetEntityLinkPacket packet);
@Shadow protected abstract ParseResults<SharedSuggestionProvider> parseCommand(String command);
@Shadow public abstract void handleRotateMob(ClientboundRotateHeadPacket packet);
@Shadow public abstract void handleMoveEntity(ClientboundMoveEntityPacket packet);
@Shadow public abstract void handleTeleportEntity(ClientboundTeleportEntityPacket packet);
@Unique
private Entity edg$cachedMount = null;
@ -91,26 +116,149 @@ public abstract class MixinClientPacketListener implements IEDGClientPacketListe
if (playerinfo == null) {
PlayerInfo npcPlayerInfo = getNPCPlayerInfo(packet.getPlayerId());
if (npcPlayerInfo == null) {
ClientNPCManager.cacheNPCIfNeeded(packet);
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);
edg$createNPCPlayer(packet, npcPlayerInfo);
}
} else original.call(packet);
}
@WrapMethod(
method = "handleSetEntityData"
)
private void edg$handleSetEntityData(ClientboundSetEntityDataPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.id());
if (entity == null) {
ClientNPCManager.cacheEntityDataIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleSetEntityMotion"
)
private void edg$handleSetEntityMotion(ClientboundSetEntityMotionPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getId());
if (entity == null) {
ClientNPCManager.cacheMotionIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleTeleportEntity"
)
private void edg$handleTeleportEntity(ClientboundTeleportEntityPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getId());
if (entity == null) {
ClientNPCManager.cacheTeleportEntityIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleMoveEntity"
)
private void edg$handleMoveEntity(ClientboundMoveEntityPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = packet.getEntity(this.level);
if (entity == null) {
ClientNPCManager.cacheMoveEntityIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleUpdateAttributes"
)
private void edg$handleUpdateAttributes(ClientboundUpdateAttributesPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getEntityId());
if (entity == null) {
ClientNPCManager.cacheAttributesIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleSetEntityPassengersPacket"
)
private void edg$handleSetEntityPassengersPacket(ClientboundSetPassengersPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getVehicle());
if (entity == null) {
ClientNPCManager.cachePassengersIfNeeded(packet);
original.call(packet);
} else {
for(int i : packet.getPassengers()) {
Entity entity1 = this.level.getEntity(i);
if (entity1 == null) {
ClientNPCManager.cachePassengersIfNeeded(packet);
break;
}
}
original.call(packet);
}
}
@WrapMethod(
method = "handleSetEquipment"
)
private void edg$handleSetEquipment(ClientboundSetEquipmentPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getEntity());
if (entity == null) {
ClientNPCManager.cacheEquipmentIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleEntityLinkPacket"
)
private void edg$handleEntityLinkPacket(ClientboundSetEntityLinkPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = this.level.getEntity(packet.getSourceId());
if (entity == null) {
ClientNPCManager.cacheLinkIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@WrapMethod(
method = "handleRotateMob"
)
private void edg$handleRotateMob(ClientboundRotateHeadPacket packet, Operation<Void> original){
PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
Entity entity = packet.getEntity(this.level);
if (entity == null) {
ClientNPCManager.cacheHeadRotationIfNeeded(packet);
original.call(packet);
} else original.call(packet);
}
@Override
public void edg$createNPCPlayer(@NotNull ClientboundAddPlayerPacket packet, @NotNull PlayerInfo npcPlayerInfo) {
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);
}
@SuppressWarnings({"unchecked", "SuspiciousMethodCalls"})
@WrapOperation(
method = "handlePlayerInfoUpdate",
@ -138,4 +286,49 @@ public abstract class MixinClientPacketListener implements IEDGClientPacketListe
public Set<PlayerInfo> getListedNPCPlayers() {
return listedPlayers;
}
@Override
public void edg$setEntityMotion(ClientboundSetEntityMotionPacket packet) {
handleSetEntityMotion(packet);
}
@Override
public void edg$setEntityData(ClientboundSetEntityDataPacket packet) {
handleSetEntityData(packet);
}
@Override
public void edg$updateAttributes(ClientboundUpdateAttributesPacket packet) {
handleUpdateAttributes(packet);
}
@Override
public void edg$setEquipment(ClientboundSetEquipmentPacket packet) {
handleSetEquipment(packet);
}
@Override
public void edg$setPassengers(ClientboundSetPassengersPacket packet) {
handleSetEntityPassengersPacket(packet);
}
@Override
public void edg$setEntityLink(ClientboundSetEntityLinkPacket packet) {
handleEntityLinkPacket(packet);
}
@Override
public void edg$rotateHead(ClientboundRotateHeadPacket packet) {
handleRotateMob(packet);
}
@Override
public void edg$moveEntity(ClientboundMoveEntityPacket packet) {
handleMoveEntity(packet);
}
@Override
public void edg$teleportEntity(ClientboundTeleportEntityPacket packet) {
handleTeleportEntity(packet);
}
}

View File

@ -30,7 +30,7 @@ public abstract class MixinGameTestServer implements IEDGMinecraftServer {
@WrapOperation(
method = "initServer",
at = @At(
value = "HEAD",
value = "INVOKE",
target = "Lnet/minecraft/gametest/framework/GameTestServer;setPlayerList(Lnet/minecraft/server/players/PlayerList;)V"
)
)

View File

@ -1,3 +1,19 @@
/*
* 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;

View File

@ -83,8 +83,7 @@ public abstract class MixinServerPlayer extends Player implements IEDGEntity {
if (data.getDeviceMainBlockPos() != null && level().getBlockEntity(data.getDeviceMainBlockPos()) instanceof BaseSeatBlockEntity seatBe) {
if (isCreative() || forced || (seatBe.hasLockCode() && Objects.equals(seatBe.getLockCode(), lock)) || !seatBe.hasLockCode()) {
Entity entity = this.getVehicle();
this.removeVehicle();
if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) {
if (entity != null && !this.level().isClientSide) {
Vec3 vec3;
if (this.isRemoved()) {
vec3 = this.position();

View File

@ -17,6 +17,7 @@
package top.r3944realms.eroticdungeongame.util;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.network.protocol.game.*;
import javax.annotation.Nullable;
import java.util.Collection;
@ -26,17 +27,22 @@ import java.util.UUID;
public interface IEDGClientPacketListener {
Map<UUID, PlayerInfo> getNPCPlayerInfoMap();
Set<PlayerInfo> getListedNPCPlayers();
default Collection<PlayerInfo> getOnlineNPCPlayers() {
return getNPCPlayerInfoMap().values();
}
default Collection<UUID> getOnlineNPCPlayerIds() {
return getNPCPlayerInfoMap().keySet();
}
@Nullable
default PlayerInfo getNPCPlayerInfo(UUID uniqueId) {
return getNPCPlayerInfoMap().get(uniqueId);
}
@Nullable
default PlayerInfo getNPCPlayerInfo(String name) {
for(PlayerInfo playerinfo : getNPCPlayerInfoMap().values()) {
@ -46,4 +52,26 @@ public interface IEDGClientPacketListener {
}
return null;
}
void edg$createNPCPlayer(ClientboundAddPlayerPacket packet, PlayerInfo playerInfo);
// void edg$createEntity(ClientboundAddEntityPacket packet);
void edg$setEntityMotion(ClientboundSetEntityMotionPacket packet);
void edg$setEntityData(ClientboundSetEntityDataPacket packet);
void edg$updateAttributes(ClientboundUpdateAttributesPacket packet);
void edg$setEquipment(ClientboundSetEquipmentPacket packet);
void edg$setPassengers(ClientboundSetPassengersPacket packet);
void edg$setEntityLink(ClientboundSetEntityLinkPacket packet);
void edg$rotateHead(ClientboundRotateHeadPacket packet);
void edg$moveEntity(ClientboundMoveEntityPacket movePacket);
void edg$teleportEntity(ClientboundTeleportEntityPacket teleportPacket);
}