/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package top.r3944realms.superleadrope;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.EntityLeaveLevelEvent;
import net.minecraftforge.event.entity.EntityTeleportEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.api.SuperLeadRopeApi;
import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashData;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashState;
import top.r3944realms.superleadrope.api.type.capabilty.LeashInfo;
import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.config.LeashConfigManager;
import top.r3944realms.superleadrope.content.capability.CapabilityHandler;
import top.r3944realms.superleadrope.content.capability.CapabilityRemainder;
import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.capability.inter.IEternalPotato;
import top.r3944realms.superleadrope.content.command.MotionCommand;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.content.gamerule.server.TeleportWithLeashedEntities;
import top.r3944realms.superleadrope.content.item.EternalPotatoItem;
import top.r3944realms.superleadrope.content.item.SuperLeadRopeItem;
import top.r3944realms.superleadrope.core.leash.LeashInteractHandler;
import top.r3944realms.superleadrope.core.leash.LeashSyncManager;
import top.r3944realms.superleadrope.core.potato.EternalPotatoFacade;
import top.r3944realms.superleadrope.core.register.SLPGameruleRegistry;
import top.r3944realms.superleadrope.core.register.SLPItems;
import top.r3944realms.superleadrope.core.util.PotatoMode;
import top.r3944realms.superleadrope.core.util.PotatoModeHelper;
import top.r3944realms.superleadrope.datagen.data.SLPLangKeyValue;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
import top.r3944realms.superleadrope.util.capability.LeashStateInnerAPI;
import top.r3944realms.superleadrope.util.model.RidingRelationship;
import top.r3944realms.superleadrope.util.riding.RidingApplier;
import top.r3944realms.superleadrope.util.riding.RidingDismounts;
import top.r3944realms.superleadrope.util.riding.RidingFinder;
import top.r3944realms.superleadrope.util.riding.RidingSaver;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
/**
* The type Common event handler.
*/
public class CommonEventHandler {
/**
* The constant leashConfigManager.
*/
public volatile static LeashConfigManager leashConfigManager;
/**
* The type Game.
*/
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.FORGE)
public static class Game {
/**
* On entity join world.
*
* @param event the event
*/
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
if (LeashDataImpl.isLeashable(entity)) {
LeashDataInnerAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::track);
LeashStateInnerAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::track);
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.Data.forEach(i -> {
if (i.isLeashedBy(serverPlayer) && i.isInDelayedLeash(serverPlayer.getUUID())) {
i.removeDelayedLeash(serverPlayer.getUUID());//重新加入去除延迟
}
});
}
}
}
/**
* On entity leave world.
*
* @param event the event
*/
@SubscribeEvent
public static void onEntityLeaveWorld(EntityLeaveLevelEvent event) {
Entity entity = event.getEntity();
if (entity.level().isClientSide) return;
if (LeashDataImpl.isLeashable(entity)) {
if (entity instanceof ServerPlayer serverPlayer) {
LeashSyncManager.Data.forEach(i -> {
if(i.isLeashedBy(serverPlayer)) {
i.addDelayedLeash(serverPlayer); //添加延迟
}
});
}
LeashDataInnerAPI.getLeashData(entity).ifPresent(LeashSyncManager.Data::untrack);
LeashStateInnerAPI.getLeashState(entity).ifPresent(LeashSyncManager.State::untrack);
}
}
/**
* On player logged in.
*
* @param event the event
*/
@SubscribeEvent
public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) return; // 只处理服务端
for (ItemStack stack : player.getInventory().items) {
if (stack.getItem() instanceof EternalPotatoItem) {
stack.getCapability(CapabilityHandler.ETERNAL_POTATO_CAP).ifPresent(cap -> {
// 物品已绑定主人时,恢复服务端上下文
if (cap.getOwnerUUID() != null && cap.getOwnerUUID().equals(player.getUUID())) {
cap.bindSyncContext(player); // 只绑定服务端玩家
cap.syncToClient(player); // 由服务端发包给客户端
}
});
}
}
}
/**
* On player right hit on block.
*
* @param event the event
*/
@SubscribeEvent
public static void onPlayerRightHitOnBlock(PlayerInteractEvent.RightClickBlock event) {
Level level = event.getLevel();
BlockPos blockPos = event.getHitVec().getBlockPos();
BlockState blockState = level.getBlockState(blockPos);
Player player = event.getEntity();
ItemStack itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
if (SuperLeashKnotEntity.isSupportBlock(blockState)) {
boolean shouldConsume = SuperLeadRopeItem.bindToBlock(player, level, blockPos, event.getItemStack(), itemInHand.is(SLPItems.SUPER_LEAD_ROPE.get()));
if (shouldConsume) {
event.setCancellationResult(InteractionResult.SUCCESS);
event.setCanceled(true);
}
}
}
/**
* Gets server level.
*
* @return the server level
*/
public static ServerLevel getServerLevel() {
return sl;
}
private static ServerLevel sl;
/**
* On server starting.
*
* @param event the event
*/
@SubscribeEvent
public static void onServerStarting(ServerStartingEvent event) {
PotatoMode mode = PotatoModeHelper.getCurrentMode();
EternalPotatoFacade.init(mode, true); // 服务端
}
/**
* On world load.
*
* @param event the event
*/
@SubscribeEvent
public static void onWorldLoad(LevelEvent.Load event) {
if (event.getLevel() instanceof ServerLevel serverLevel) {
// 只在主世界挂载 SavedData
if (serverLevel.dimension() == Level.OVERWORLD) {
EternalPotatoFacade.initSavedData(serverLevel);
RidingSaver.setEntityProvider(serverLevel::getEntity);
sl = serverLevel;
}
}
}
/**
* On world unload.
*
* @param event the event
*/
@SubscribeEvent
public static void onWorldUnload(LevelEvent.Unload event) {
if (event.getLevel() instanceof ServerLevel serverLevel) {
// 只在主世界卸载时清空
if (serverLevel.dimension() == Level.OVERWORLD) {
EternalPotatoFacade.clear();
sl = null;
}
}
}
/**
* On server stopping.
*
* @param event the event
*/
// 服务器关闭
@SubscribeEvent
public static void onServerStopping(ServerStoppingEvent event) {
EternalPotatoFacade.clear();
}
/**
* On item drop.
*
* @param event the event
*/
@SubscribeEvent
public static void onItemDrop(ItemTossEvent event) {
Player player = event.getPlayer();
ItemEntity entityItem = event.getEntity();
ItemStack stack = entityItem.getItem();
if (!(stack.getItem() instanceof EternalPotatoItem)) return;
if (player.level().isClientSide) return; // 只处理服务端
UUID uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
IEternalPotato cap = EternalPotatoFacade.getOrCreate(uuid);
if (player instanceof ServerPlayer serverPlayer) {
cap.bindSyncContext(serverPlayer); // 服务端绑定
}
EternalPotatoItem.ensureItemInInventory(player, stack);
// 移除地面实体
BlockPos spawnPos = entityItem.level().getSharedSpawnPos();
entityItem.setPos(spawnPos.getX(), -394, spawnPos.getZ());
// 处罚逻辑
if (cap.getOwnerUUID() != null
&& cap.getOwnerUUID().equals(player.getUUID())
&& !player.getAbilities().instabuild) {
cap.beginInit();
cap.setPendingPunishments(cap.getPendingPunishments() + 2);
cap.endInit();
player.sendSystemMessage(
net.minecraft.network.chat.Component.translatable(SLPLangKeyValue.EP_CANNOT_DROP.getKey(), 2)
);
}
}
/**
* On item pickup.
*
* @param event the event
*/
@SubscribeEvent
public static void onItemPickup(@NotNull PlayerEvent.ItemPickupEvent event) {
Player player = event.getEntity();
ItemStack stack = event.getStack();
if (player.level().isClientSide) return; // 客户端不处理
if (!(stack.getItem() instanceof EternalPotatoItem)) return;
if (player.getAbilities().instabuild) return;
UUID uuid = EternalPotatoItem.getOrCreateItemUUID(stack);
IEternalPotato cap = EternalPotatoFacade.getOrCreate(uuid);
if (player instanceof ServerPlayer serverPlayer) {
cap.bindSyncContext(serverPlayer); // 服务端绑定
}
// 如果玩家不是主人,显示提示
if (cap.getOwnerUUID() != null && !cap.getOwnerUUID().equals(player.getUUID())) {
player.displayClientMessage(
net.minecraft.network.chat.Component.translatable(SLPLangKeyValue.EP_PICKUP_NOT_OWNER.getKey()),
true
);
}
}
/**
* On entity teleport.
*
* @param event the event
*/
@SubscribeEvent
public static void onEntityTeleport(EntityTeleportEvent event) {
Entity telEntity = event.getEntity();
Vec3 targetPos = event.getTarget();
Level level = telEntity.level();
if (!(level instanceof ServerLevel serverLevel)) return;
// 获取范围内可被拴住实体
List entities = SuperLeadRopeApi.leashableInArea(telEntity);
//规则关闭则禁止
if(!SLPGameruleRegistry.getGameruleBoolValue(event.getEntity().level(), TeleportWithLeashedEntities.ID)) {
entities.forEach(entity -> LeashDataInnerAPI.LeashOperations.detach(entity, telEntity));
return;
}
for (Entity beLeashedEntity : entities) {
// --- 保存状态快照 ---
if (MinecraftForge.EVENT_BUS.post(new SuperLeadRopeEvent.teleportWithHolder(beLeashedEntity, telEntity, beLeashedEntity.level(), level, beLeashedEntity.position(), targetPos))) continue;
Pose originalPose = beLeashedEntity.getPose();
boolean originalIsSprinting = beLeashedEntity.isSprinting();
float originalYaw = beLeashedEntity.getYRot();
float originalPitch = beLeashedEntity.getXRot();
Vec3 originalDeltaMovement = beLeashedEntity.getDeltaMovement();
AtomicReference originalLeashInfo = new AtomicReference<>();
LeashDataInnerAPI.getLeashData(beLeashedEntity).ifPresent(data -> {
originalLeashInfo.set(data.getLeashInfo(telEntity).orElse(null));
data.removeLeash(telEntity);
});
// --- 保存骑乘关系(可修改列表) ---
RidingRelationship originalRidingRelationship = RidingSaver.save(beLeashedEntity, true);
// --- 解除骑乘 ---
List allPassengers = RidingFinder.getEntityFromRidingShip(originalRidingRelationship, serverLevel::getEntity);
RidingDismounts.dismountEntities(allPassengers);
// --- 传送实体及乘客 ---
for (Entity entity : allPassengers) {
if (entity.level() != serverLevel) {
entity.teleportTo(serverLevel, targetPos.x, targetPos.y, targetPos.z,
Collections.emptySet(), originalYaw, originalPitch);
} else if (entity instanceof ServerPlayer player) {
player.connection.teleport(targetPos.x, targetPos.y, targetPos.z,
originalYaw, originalPitch, Collections.emptySet());
} else {
entity.teleportTo(serverLevel, targetPos.x, targetPos.y, targetPos.z,
Collections.emptySet(), originalYaw, originalPitch);
}
}
// --- 恢复状态 ---
for (Entity entity : allPassengers) {
entity.setDeltaMovement(originalDeltaMovement);
entity.setSprinting(originalIsSprinting);
entity.setPose(originalPose);
}
// --- 将holder替换 ---
LeashInfo leashInfo = Optional.ofNullable(originalLeashInfo.get())
.orElse(LeashInfo.EMPTY);
LeashDataInnerAPI.LeashOperations.attachWithInfo(beLeashedEntity, telEntity, leashInfo);
// --- 重新应用骑乘关系,仅保留白名单根载具 ---
RidingRelationship filteredRelationship = RidingSaver.filterByWhitelistRoot(originalRidingRelationship);
RidingApplier.applyRidingRelationship(filteredRelationship, serverLevel::getEntity);
}
}
/**
* On player clone.
*
* @param event the event
*/
@SubscribeEvent
public static void onPlayerClone(PlayerEvent.Clone event) {
CapabilityRemainder.onPlayerClone(event);
}
private static int tickCounter = 0;
/**
* On server tick.
*
* @param event the event
*/
@SubscribeEvent
public static void onServerTick(TickEvent.ServerTickEvent event) {
if (event.phase == TickEvent.Phase.END) {
tickCounter++;
// 每10 tick标记为脏(needsSync)
if (tickCounter % 10 == 0) {
LeashSyncManager.Data.forEach(ILeashData::markForSync);
LeashSyncManager.State.forEach(ILeashState::markForSync);
}
// 定期同步检查
LeashSyncManager.Data.forEach(ILeashData::checkSync);
LeashSyncManager.State.forEach(ILeashState::checkSync);
// 应用物理拉力/效果
LeashSyncManager.Data.forEach(ILeashData::applyLeashForces);
}
}
/**
* On entity attack.
*
* @param event the event
*/
@SubscribeEvent
public static void onEntityAttack (AttackEntityEvent event) {
LeashInteractHandler.onEntityLeftInteract(event.getEntity().level(), event.getTarget(), event.getEntity(), event);
}
/**
* On entity interact.
*
* @param event the event
*/
@SubscribeEvent
public static void onEntityInteract (PlayerInteractEvent.EntityInteract event) {
LeashInteractHandler.onEntityRightInteract(event.getLevel(), event.getHand(), event.getTarget(), event.getEntity(), event); //处理实体互动
}
/**
* Attach capability.
*
* @param event the event
*/
@SubscribeEvent
public static void attachCapability(AttachCapabilitiesEvent> event) {
CapabilityHandler.attachCapability(event);
}
/**
* On register command.
*
* @param event the event
*/
@SubscribeEvent
public static void onRegisterCommand (RegisterCommandsEvent event) {
CommandDispatcher dispatcher = event.getDispatcher();
MotionCommand.register(dispatcher);
}
}
/**
* The type Mod.
*/
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = SuperLeadRope.MOD_ID, bus= net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD)
public static class Mod {
/**
* On fml common init.
*
* @param event the event
*/
@SubscribeEvent
public static void onFMLCommonInit(FMLCommonSetupEvent event) {
event.enqueueWork(Mod::checkAndSet);
event.enqueueWork(SLPGameruleRegistry::register);//规则注册
}
/**
* Register capability.
*
* @param event the event
*/
@SubscribeEvent
public static void registerCapability(RegisterCapabilitiesEvent event) {
CapabilityHandler.registerCapability(event);
}
/**
* On creative tab.
*
* @param event the event
*/
@SubscribeEvent
public static void onCreativeTab (BuildCreativeModeTabContentsEvent event) {
if (event.getTabKey() == CreativeModeTabs.TOOLS_AND_UTILITIES) {
event.accept(SLPItems.SUPER_LEAD_ROPE);
}
}
/**
* On config reloading.
*
* @param event the event
*/
@SubscribeEvent
public void onConfigReloading(ModConfigEvent.Reloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
SuperLeadRope.logger.debug("Config reloading detected...");
}
}
private static void checkAndSet() {
if (leashConfigManager == null) {
synchronized (LeashConfigManager.class) {
if (leashConfigManager == null) {
leashConfigManager = new LeashConfigManager();
}
}
}
}
/**
* On config loaded.
*
* @param event the event
*/
@SubscribeEvent
public void onConfigLoaded(ModConfigEvent.Loading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
checkAndSet();
LeashConfigManager.loading(leashConfigManager);
}
}
/**
* On config reloaded.
*
* @param event the event
*/
@SubscribeEvent
public void onConfigReloaded(ModConfigEvent.Reloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
checkAndSet();
LeashConfigManager.reloading(leashConfigManager);
}
}
/**
* On config unloaded.
*
* @param event the event
*/
@SubscribeEvent
public void onConfigUnloaded(ModConfigEvent.Unloading event) {
if (event.getConfig().getSpec() == LeashCommonConfig.SPEC) {
LeashConfigManager.unloading(leashConfigManager);
}
}
}
}