From af2e4c113bf251f04ec527d758513b6d7111b1a0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Mon, 16 Mar 2026 22:16:10 +0100 Subject: [PATCH] Add RInteraction --- .../src/de/steamwar/entity/REntityServer.java | 66 +++++----- .../src/de/steamwar/entity/RInteraction.java | 119 ++++++++++++++++++ 2 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java index 3ba28d38..6dc75af3 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntityServer.java @@ -19,8 +19,8 @@ package de.steamwar.entity; -import de.steamwar.Reflection; import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.Reflection; import de.steamwar.core.Core; import de.steamwar.core.FlatteningWrapper; import org.bukkit.Bukkit; @@ -52,8 +52,9 @@ public class REntityServer implements Listener { private static final Class useEntityEnumAction = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action"); private static final Reflection.Field useEntityAction = Reflection.getField(useEntity, useEntityEnumAction, 0); private static final Function getEntityAction; + static { - if(Core.getVersion() > 15) { + if (Core.getVersion() > 15) { Class useEntityEnumActionType = Reflection.getClass("net.minecraft.network.protocol.game.ServerboundInteractPacket$ActionType"); Reflection.Method useEntityGetAction = Reflection.getTypedMethod(useEntityEnumAction, null, useEntityEnumActionType); getEntityAction = value -> ((Enum) useEntityGetAction.invoke(value)).ordinal(); @@ -71,6 +72,9 @@ public class REntityServer implements Listener { private EntityActionListener callback = null; private final Set playersThatClicked = Collections.synchronizedSet(new HashSet<>()); + private static final String DE_STEAMWAR_ENTITY_RINTERACTION = "de.steamwar.entity.RInteraction"; + private Reflection.Field interactionField = Reflection.getField(DE_STEAMWAR_ENTITY_RINTERACTION, "interaction", BiConsumer.class); + private final BiFunction filter = (player, packet) -> { REntity entity = entityMap.get(useEntityTarget.get(packet)); if (entity == null) @@ -83,6 +87,9 @@ public class REntityServer implements Listener { EntityAction action = getEntityAction.apply(useEntityAction.get(packet)) == 1 ? EntityAction.ATTACK : EntityAction.INTERACT; Bukkit.getScheduler().runTask(Core.getInstance(), () -> { playersThatClicked.remove(player); + if (entity.getClass().getTypeName().equals(DE_STEAMWAR_ENTITY_RINTERACTION)) { + interactionField.get(entity).accept(player, action); + } callback.onAction(player, entity, action); }); return null; @@ -96,7 +103,7 @@ public class REntityServer implements Listener { boolean uninitialized = this.callback == null; this.callback = callback; - if(uninitialized) + if (uninitialized) TinyProtocol.instance.addFilter(useEntity, filter); } @@ -121,13 +128,16 @@ public class REntityServer implements Listener { public void close() { TinyProtocol.instance.removeFilter(useEntity, filter); - for(Player player : lastLocation.keySet().toArray(new Player[0])) { + for (Player player : lastLocation.keySet().toArray(new Player[0])) { removePlayer(player); } HandlerList.unregisterAll(this); } void addEntity(REntity entity) { + if (callback == null && entity.getClass().getTypeName().equals(DE_STEAMWAR_ENTITY_RINTERACTION)) { + setCallback((player, entity1, action) -> {}); + } entityMap.put(entity.entityId, entity); addEntityToChunk(entity); entity.list(packet -> updateEntity(entity, packet)); @@ -137,10 +147,10 @@ public class REntityServer implements Listener { void preEntityMove(REntity entity, double toX, double toZ) { long fromId = entityToId(entity); long toId = posToId(toX, toZ); - if(fromId == toId) + if (fromId == toId) return; - if(!entity.isHidden()) + if (!entity.isHidden()) onMissing(players.get(fromId), players.get(toId), entity::despawn); onMissing(players.get(fromId), players.get(toId), entity::delist); removeEntityFromChunk(entity); @@ -149,20 +159,20 @@ public class REntityServer implements Listener { void postEntityMove(REntity entity, double fromX, double fromZ) { long fromId = posToId(fromX, fromZ); long toId = entityToId(entity); - if(fromId == toId) + if (fromId == toId) return; addEntityToChunk(entity); onMissing(players.get(toId), players.get(fromId), entity::list); - if(!entity.isHidden()) + if (!entity.isHidden()) onMissing(players.get(toId), players.get(fromId), entity::spawn); } void updateEntity(REntity entity, Object packet) { - if(entity.isHidden()) + if (entity.isHidden()) return; - for(Player player : players.getOrDefault(entityToId(entity), emptyPlayers)) { + for (Player player : players.getOrDefault(entityToId(entity), emptyPlayers)) { TinyProtocol.instance.sendPacket(player, packet); } } @@ -191,7 +201,7 @@ public class REntityServer implements Listener { HashSet entitiesInChunk = entities.get(id); if (entitiesInChunk == null) return; entitiesInChunk.remove(entity); - if(entitiesInChunk.isEmpty()) + if (entitiesInChunk.isEmpty()) entities.remove(id); } @@ -200,28 +210,28 @@ public class REntityServer implements Listener { Player player = e.getPlayer(); Location from = lastLocation.get(player); Location to = e.getTo(); - if(from == null || to == null) + if (from == null || to == null) return; int fromX = posToChunk(from.getX()); int fromZ = posToChunk(from.getZ()); int toX = posToChunk(to.getX()); int toZ = posToChunk(to.getZ()); - if(fromX == toX && fromZ == toZ) + if (fromX == toX && fromZ == toZ) return; lastLocation.put(player, to); int toViewDistance = viewRadius(player); forChunkInView(player, from, (x, z) -> { - if(Math.abs(x - toX) > toViewDistance || Math.abs(z - toZ) > toViewDistance) { + if (Math.abs(x - toX) > toViewDistance || Math.abs(z - toZ) > toViewDistance) { removePlayerFromChunk(player, x, z); } }); int fromViewDistance = this.viewDistance.put(player, toViewDistance); forChunkInView(player, to, (x, z) -> { - if(Math.abs(x - fromX) > fromViewDistance || Math.abs(z - fromZ) > fromViewDistance) { + if (Math.abs(x - fromX) > fromViewDistance || Math.abs(z - fromZ) > fromViewDistance) { addPlayerToChunk(player, x, z); } }); @@ -231,25 +241,25 @@ public class REntityServer implements Listener { public void onQuit(PlayerQuitEvent e) { Player player = e.getPlayer(); Location location = lastLocation.remove(player); - if(location == null) + if (location == null) return; forChunkInView(player, location, (x, z) -> { long id = chunkToId(x, z); Set playersInChunk = players.get(id); playersInChunk.remove(player); - if(playersInChunk.isEmpty()) + if (playersInChunk.isEmpty()) players.remove(id); }); viewDistance.remove(player); } private void onMissing(Set of, Set in, Consumer> packetProvider) { - if(of == null) + if (of == null) return; - for(Player player : of) { - if(in == null || !in.contains(player)) { + for (Player player : of) { + if (in == null || !in.contains(player)) { packetProvider.accept(packet -> TinyProtocol.instance.sendPacket(player, packet)); } } @@ -260,8 +270,8 @@ public class REntityServer implements Listener { int chunkZ = posToChunk(location.getZ()); int viewDistance = this.viewDistance.get(player); - for(int x = chunkX - viewDistance; x <= chunkX + viewDistance; x++) { - for(int z = chunkZ - viewDistance; z <= chunkZ + viewDistance; z++) { + for (int x = chunkX - viewDistance; x <= chunkX + viewDistance; x++) { + for (int z = chunkZ - viewDistance; z <= chunkZ + viewDistance; z++) { func.accept(x, z); } } @@ -270,9 +280,9 @@ public class REntityServer implements Listener { private void addPlayerToChunk(Player player, int x, int z) { long id = chunkToId(x, z); players.computeIfAbsent(id, i -> new HashSet<>()).add(player); - for(REntity entity : entities.getOrDefault(id, emptyEntities)) { + for (REntity entity : entities.getOrDefault(id, emptyEntities)) { entity.list(packet -> TinyProtocol.instance.sendPacket(player, packet)); - if(!entity.isHidden()) + if (!entity.isHidden()) entity.spawn(packet -> TinyProtocol.instance.sendPacket(player, packet)); } } @@ -282,11 +292,11 @@ public class REntityServer implements Listener { Set playersInChunk = players.get(id); playersInChunk.remove(player); - if(playersInChunk.isEmpty()) + if (playersInChunk.isEmpty()) players.remove(id); - for(REntity entity : entities.getOrDefault(id, emptyEntities)) { - if(!entity.isHidden()) + for (REntity entity : entities.getOrDefault(id, emptyEntities)) { + if (!entity.isHidden()) entity.despawn(packet -> TinyProtocol.instance.sendPacket(player, packet)); entity.delist(packet -> TinyProtocol.instance.sendPacket(player, packet)); } @@ -299,7 +309,7 @@ public class REntityServer implements Listener { } private int posToChunk(double coord) { - return (int)(coord / 16) - (coord < 0 ? 1 : 0); + return (int) (coord / 16) - (coord < 0 ? 1 : 0); } private int viewRadius(Player player) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java new file mode 100644 index 00000000..530aca91 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java @@ -0,0 +1,119 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.core.BountifulWrapper; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * !! This class cannot be used in Versions lower than or equal to 1.19.4 !! + */ +@Getter +public class RInteraction extends REntity { + + protected final Consumer updatePacketSink = o -> server.updateEntity(this, o); + + @Setter + @Getter(AccessLevel.PRIVATE) + protected BiConsumer interaction; + + private float interactionWidth = 1.0f; + private float interactionHeight = 1.0f; + private boolean responsive = false; + + public RInteraction(REntityServer server, Location location) { + super(server, EntityType.INTERACTION, location); + server.addEntity(this); + } + + @Override + protected void postSpawn(Consumer packetSink) { + super.postSpawn(packetSink); + sendPacket(packetSink, + this::getInteractionWidthData, + this::getInteractionHeightData, + this::getResponsiveData + ); + } + + @SafeVarargs + protected final void sendPacket(Consumer packetSink, BiConsumer>... dataSinkSinks) { + List keyValueData = new ArrayList<>(); + boolean ignoreDefault = packetSink == updatePacketSink; + for (BiConsumer> dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { + keyValueData.add(dataWatcher); + keyValueData.add(value); + }); + } + if (!keyValueData.isEmpty()) { + packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + } + } + + public void setInteractionWidth(float interactionWidth) { + this.interactionWidth = interactionWidth; + sendPacket(updatePacketSink, this::getInteractionWidthData); + } + + private static final Object interactionWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(8, Float.class); + + private void getInteractionWidthData(boolean ignoreDefault, BiConsumer dataSink) { + if (ignoreDefault || interactionWidth != 1.0) { + dataSink.accept(interactionWidthWatcher, interactionWidth); + } + } + + public void setInteractionHeight(float interactionHeight) { + this.interactionHeight = interactionHeight; + sendPacket(updatePacketSink, this::getInteractionHeightData); + } + + private static final Object interactionHeightWatcher = BountifulWrapper.impl.getDataWatcherObject(9, Float.class); + + private void getInteractionHeightData(boolean ignoreDefault, BiConsumer dataSink) { + if (ignoreDefault || interactionHeight != 1.0) { + dataSink.accept(interactionHeightWatcher, interactionHeight); + } + } + + public void setResponsive(boolean responsive) { + this.responsive = responsive; + sendPacket(updatePacketSink, this::getResponsiveData); + } + + private static final Object responsiveWatcher = BountifulWrapper.impl.getDataWatcherObject(10, Boolean.class); + + private void getResponsiveData(boolean ignoreDefault, BiConsumer dataSink) { + if (ignoreDefault || !responsive) { + dataSink.accept(responsiveWatcher, responsive); + } + } +}