Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rapid packet-based entity data changes #2540

Merged
merged 25 commits into from Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -456,6 +456,10 @@ public int mapInternalEntityDataName(Entity entity, String name) {
throw new UnsupportedOperationException();
}

public List<Object> convertInternalEntityDataValues(Entity entity, Map<Integer, ObjectTag> data) {
throw new UnsupportedOperationException();
}

public void modifyInternalEntityData(Entity entity, Map<Integer, ObjectTag> internalData) {
throw new UnsupportedOperationException();
}
Expand Down
Expand Up @@ -4,6 +4,7 @@
import com.denizenscript.denizen.scripts.commands.entity.TeleportCommand;
import com.denizenscript.denizen.utilities.maps.MapImage;
import com.denizenscript.denizencore.objects.core.ColorTag;
import it.unimi.dsi.fastutil.Pair;
import org.bukkit.Bukkit;
import org.bukkit.EntityEffect;
import org.bukkit.Location;
Expand Down Expand Up @@ -159,4 +160,8 @@ default void sendRelativePositionPacket(Player player, double x, double y, doubl
default void sendRelativeLookPacket(Player player, float yaw, float pitch) {
throw new UnsupportedOperationException();
}

default void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {
throw new UnsupportedOperationException();
}
}
Expand Up @@ -10,6 +10,7 @@
import com.denizenscript.denizen.objects.properties.entity.EntityAge;
import com.denizenscript.denizen.objects.properties.entity.EntityColor;
import com.denizenscript.denizen.objects.properties.entity.EntityTame;
import com.denizenscript.denizen.scripts.commands.entity.FakeInternalDataCommand;
import com.denizenscript.denizen.scripts.commands.player.DisguiseCommand;
import com.denizenscript.denizen.scripts.containers.core.EntityScriptContainer;
import com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper;
Expand Down Expand Up @@ -3004,16 +3005,7 @@ else if (object.getBukkitEntity() instanceof Hanging) {
// (note that it documents the values that eventually get sent to the client, so the input this expects might be slightly different in some cases).
// -->
tagProcessor.registerMechanism("internal_data", false, MapTag.class, (object, mechanism, input) -> {
Map<Integer, ObjectTag> internalData = new HashMap<>(input.size());
for (Map.Entry<StringHolder, ObjectTag> entry : input.entrySet()) {
int id = NMSHandler.entityHelper.mapInternalEntityDataName(object.getBukkitEntity(), entry.getKey().low);
if (id == -1) {
mechanism.echoError("Invalid internal data key: " + entry.getKey());
continue;
}
internalData.put(id, entry.getValue());
}
NMSHandler.entityHelper.modifyInternalEntityData(object.getBukkitEntity(), internalData);
NMSHandler.entityHelper.modifyInternalEntityData(object.getBukkitEntity(), FakeInternalDataCommand.parseEntityDataMap(object.getBukkitEntity(), input));
});
}
}
Expand Down
Expand Up @@ -92,6 +92,7 @@ public static void registerCommands() {
registerCommand(CastCommand.class);
registerCommand(EquipCommand.class);
registerCommand(FakeEquipCommand.class);
registerCommand(FakeInternalDataCommand.class);
registerCommand(FeedCommand.class);
registerCommand(FlyCommand.class);
registerCommand(FollowCommand.class);
Expand Down
@@ -0,0 +1,73 @@
package com.denizenscript.denizen.scripts.commands.entity;

import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.objects.EntityTag;
import com.denizenscript.denizen.objects.PlayerTag;
import com.denizenscript.denizencore.DenizenCore;
import com.denizenscript.denizencore.objects.ObjectTag;
import com.denizenscript.denizencore.objects.core.DurationTag;
import com.denizenscript.denizencore.objects.core.MapTag;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;
import com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultText;
import com.denizenscript.denizencore.scripts.commands.generator.ArgName;
import com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed;
import com.denizenscript.denizencore.scripts.commands.generator.ArgSubType;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import com.denizenscript.denizencore.utilities.text.StringHolder;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FakeInternalDataCommand extends AbstractCommand {

public FakeInternalDataCommand() {
mcmonkey4eva marked this conversation as resolved.
Show resolved Hide resolved
setName("fakeinternaldata");
setSyntax("fakeinternaldata [entity:<entity>] [data:<map>|...] [for:<player>|...] (speed:<duration>)");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for arg should be optional and default to linked player

setRequiredArguments(3, 4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 3 is wrong here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to update that, fixed

autoCompile();
}

public static void autoExecute(@ArgName("entity") @ArgPrefixed EntityTag inputEntity,
@ArgName("data") @ArgPrefixed @ArgSubType(MapTag.class) List<MapTag> data,
@ArgName("for") @ArgPrefixed @ArgSubType(PlayerTag.class) List<PlayerTag> forPlayers,
@ArgName("speed") @ArgPrefixed @ArgDefaultText("0s") DurationTag speed) {
Entity entity = inputEntity.getBukkitEntity();
List<List<Object>> frames = new ArrayList<>(data.size());
for (MapTag frame : data) {
frames.add(NMSHandler.entityHelper.convertInternalEntityDataValues(entity, parseEntityDataMap(entity, frame)));
}
List<Player> sendTo = new ArrayList<>(forPlayers.size());
for (PlayerTag player : forPlayers) {
sendTo.add(player.getPlayerEntity());
}
long ms = speed.getMillis();
DenizenCore.runAsync(() -> {
try {
for (List<Object> frame : frames) {
NMSHandler.packetHelper.sendEntityDataPacket(sendTo, entity, frame);
Thread.sleep(ms);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should either note in meta that the delay is inaccurate for long runs, or switch this sleep to a more accurate delay manager.

Also considering the possibility of long runs, should also add a check to kill the loop if the player(s) are gone

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both should be fixed

}
}
catch (Exception ex) {
Debug.echoError(ex);
}
});
}

public static Map<Integer, ObjectTag> parseEntityDataMap(Entity entity, MapTag map) {
Map<Integer, ObjectTag> entityData = new HashMap<>(map.size());
for (Map.Entry<StringHolder, ObjectTag> entry : map.entrySet()) {
int id = NMSHandler.entityHelper.mapInternalEntityDataName(entity, entry.getKey().low);
if (id == -1) {
Debug.echoError("Invalid internal data key: " + entry.getKey());
continue;
}
entityData.put(id, entry.getValue());
}
return entityData;
}
}
Expand Up @@ -806,6 +806,35 @@ public int mapInternalEntityDataName(Entity entity, String name) {
return EntityDataNameMapper.getIdForName(((CraftEntity) entity).getHandle().getClass(), name);
}

public static final Field SynchedEntityData_itemsById = ReflectionHelper.getFields(SynchedEntityData.class).get(ReflectionMappingsInfo.SynchedEntityData_itemsById);

public static Int2ObjectMap<SynchedEntityData.DataItem<Object>> getDataItems(Entity entity) {
mcmonkey4eva marked this conversation as resolved.
Show resolved Hide resolved
try {
return (Int2ObjectMap<SynchedEntityData.DataItem<Object>>) SynchedEntityData_itemsById.get(((CraftEntity) entity).getHandle().getEntityData());
}
catch (IllegalAccessException e) {
throw new RuntimeException(e); // Stop the code here to avoid NPEs down the road
}
}

@Override
public List<Object> convertInternalEntityDataValues(Entity entity, Map<Integer, ObjectTag> data) {
List<Object> dataValues = new ArrayList<>(data.size());
Int2ObjectMap<SynchedEntityData.DataItem<Object>> dataItemsById = getDataItems(entity);
for (Map.Entry<Integer, ObjectTag> entry : data.entrySet()) {
SynchedEntityData.DataItem<Object> dataItem = dataItemsById.get(entry.getKey().intValue());
if (dataItem == null) {
Debug.echoError("Invalid internal data id '" + entry.getKey() + "': couldn't be matched to any internal data for entity of type '" + entity.getType() + "'.");
continue;
}
Object converted = ReflectionSetCommand.convertObjectTypeFor(dataItem.getValue().getClass(), entry.getValue());
if (converted != null) {
dataValues.add(PacketHelperImpl.createEntityData(dataItem.getAccessor(), converted));
}
}
return dataValues;
}

@Override
public void modifyInternalEntityData(Entity entity, Map<Integer, ObjectTag> internalData) {
SynchedEntityData nmsEntityData = ((CraftEntity) entity).getHandle().getEntityData();
Expand Down
Expand Up @@ -15,6 +15,8 @@
import com.denizenscript.denizencore.utilities.ReflectionHelper;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.md_5.bungee.api.ChatColor;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
Expand Down Expand Up @@ -397,18 +399,30 @@ public void sendRelativePositionPacket(Player player, double x, double y, double
}
}
ClientboundPlayerPositionPacket packet = new ClientboundPlayerPositionPacket(x, y, z, yaw, pitch, relativeMovements, 0);
DenizenNetworkManagerImpl.getNetworkManager(player).oldManager.channel.writeAndFlush(packet);
sendAsyncSafe(player, packet);
}

@Override
public void sendRelativeLookPacket(Player player, float yaw, float pitch) {
sendRelativePositionPacket(player, 0, 0, 0, yaw, pitch, null);
}

@Override
public void sendEntityDataPacket(List<Player> players, Entity entity, List<Object> data) {
ClientboundSetEntityDataPacket setEntityDataPacket = new ClientboundSetEntityDataPacket(entity.getEntityId(), (List<SynchedEntityData.DataValue<?>>) (Object) data);
for (Player player : players) {
sendAsyncSafe(player, setEntityDataPacket);
}
}

public static void send(Player player, Packet<?> packet) {
((CraftPlayer) player).getHandle().connection.send(packet);
}

public static void sendAsyncSafe(Player player, Packet<?> packet) {
DenizenNetworkManagerImpl.getNetworkManager(player).oldManager.channel.writeAndFlush(packet);
}

public static <T> SynchedEntityData.DataValue<T> createEntityData(EntityDataAccessor<T> accessor, T value) {
return new SynchedEntityData.DataValue<>(accessor.getId(), accessor.getSerializer(), value);
}
Expand Down