Skip to content

Commit

Permalink
Add mount_to arg to fakespawn command (#2513)
Browse files Browse the repository at this point in the history
* Add mount_to arg to fakespawn command

* Error if trying to mount_to entity that's not spawned

* PR fixes

* Update max fakespawn command args meta
  • Loading branch information
mergu committed Sep 2, 2023
1 parent 8cecc0e commit e2e883a
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 70 deletions.
Expand Up @@ -93,6 +93,10 @@ public void sendClimbableMaterials(Player player, List<Material> materials) {
throw new UnsupportedOperationException();
}

public void addFakePassenger(List<PlayerTag> players, Entity entity, FakeEntity fakeEntity) {
throw new UnsupportedOperationException();
}

public void refreshPlayer(Player player) {
throw new UnsupportedOperationException();
}
Expand Down
Expand Up @@ -156,7 +156,7 @@ public void startFake(PlayerTag player) {
if (!player.isOnline()) {
return;
}
fakeToSelf = FakeEntity.showFakeEntityTo(Collections.singletonList(player), as, player.getLocation(), null);
fakeToSelf = FakeEntity.showFakeEntityTo(Collections.singletonList(player), as, player.getLocation(), null, null);
NMSHandler.packetHelper.generateNoCollideTeam(player.getPlayerEntity(), fakeToSelf.entity.getUUID());
NMSHandler.packetHelper.sendEntityMetadataFlagsUpdate(player.getPlayerEntity(), player.getPlayerEntity());
new BukkitRunnable() {
Expand Down
Expand Up @@ -3,35 +3,37 @@
import com.denizenscript.denizen.objects.EntityTag;
import com.denizenscript.denizen.utilities.Utilities;
import com.denizenscript.denizen.utilities.command.TabCompleteHelper;
import com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException;
import com.denizenscript.denizencore.objects.ObjectTag;
import com.denizenscript.denizencore.scripts.commands.generator.*;
import com.denizenscript.denizencore.utilities.Deprecations;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import com.denizenscript.denizen.objects.LocationTag;
import com.denizenscript.denizen.objects.PlayerTag;
import com.denizenscript.denizen.utilities.entity.FakeEntity;
import com.denizenscript.denizencore.exceptions.InvalidArgumentsException;
import com.denizenscript.denizencore.objects.*;
import com.denizenscript.denizencore.objects.core.DurationTag;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.objects.core.ListTag;
import com.denizenscript.denizencore.scripts.ScriptEntry;
import com.denizenscript.denizencore.scripts.commands.AbstractCommand;

import java.util.Collections;
import java.util.List;

public class FakeSpawnCommand extends AbstractCommand {

public FakeSpawnCommand() {
setName("fakespawn");
setSyntax("fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (d:<duration>{10s})");
setRequiredArguments(2, 4);
setSyntax("fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (duration:<duration>{10s}) (mount_to:<entity>)");
setRequiredArguments(2, 5);
isProcedural = false;
addRemappedPrefixes("duration", "d");
addRemappedPrefixes("players", "to");
autoCompile();
}

// <--[command]
// @Name FakeSpawn
// @Syntax fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (d:<duration>{10s})
// @Syntax fakespawn [<entity>] [<location>/cancel] (players:<player>|...) (duration:<duration>{10s}) (mount_to:<entity>)
// @Required 2
// @Maximum 4
// @Maximum 5
// @Short Makes the player see a fake entity spawn that didn't actually happen.
// @Group player
//
Expand All @@ -46,6 +48,8 @@ public FakeSpawnCommand() {
// Optionally, specify how long the fake entity should remain for. If unspecified, will default to 10 seconds.
// After the duration is up, the entity will be removed from the player(s).
//
// Optionally, specify an entity to mount the fake entity to via mount_to:<entity>.
//
// @Tags
// <PlayerTag.fake_entities>
// <entry[saveName].faked_entity> returns the spawned faked entity.
Expand All @@ -54,79 +58,60 @@ public FakeSpawnCommand() {
// Use to show a fake creeper in front of the attached player.
// - fakespawn creeper <player.location.forward[5]>
//
// @Usage
// Use to spawn and mount a fake armor stand to the player.
// - fakespawn armor_stand <player.location> mount_to:<player>
//
// -->

@Override
public void addCustomTabCompletions(TabCompletionsBuilder tab) {
TabCompleteHelper.tabCompleteEntityTypes(tab);
}

@Override
public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException {
for (Argument arg : scriptEntry) {
if (!scriptEntry.hasObject("players")
&& arg.matchesPrefix("to", "players")) {
scriptEntry.addObject("players", arg.asType(ListTag.class).filter(PlayerTag.class, scriptEntry));
}
else if (arg.matchesPrefix("d", "duration")
&& arg.matchesArgumentType(DurationTag.class)) {
scriptEntry.addObject("duration", arg.asType(DurationTag.class));
}
else if (!scriptEntry.hasObject("entity")
&& arg.matchesArgumentType(EntityTag.class)) {
scriptEntry.addObject("entity", arg.asType(EntityTag.class));
}
else if (!scriptEntry.hasObject("location")
&& arg.matchesArgumentType(LocationTag.class)) {
scriptEntry.addObject("location", arg.asType(LocationTag.class));
}
else if (!scriptEntry.hasObject("cancel")
&& arg.matches("cancel")) {
scriptEntry.addObject("cancel", new ElementTag(true));
}
else {
arg.reportUnhandled();
}
public static void autoExecute(ScriptEntry scriptEntry,
@ArgName("entity") @ArgLinear ObjectTag entityObj,
@ArgName("location") @ArgLinear @ArgDefaultNull ObjectTag locationObj,
@ArgName("cancel") boolean cancel,
@ArgName("players") @ArgPrefixed @ArgDefaultNull @ArgSubType(PlayerTag.class) List<PlayerTag> players,
@ArgName("duration") @ArgPrefixed @ArgDefaultText("10s") DurationTag duration,
@ArgName("mount_to") @ArgPrefixed @ArgDefaultNull EntityTag vehicle) {
if (locationObj != null && entityObj.identify().startsWith("l@")) { // Compensate for legacy entity/location out-of-order support
ObjectTag swap = locationObj;
locationObj = entityObj;
entityObj = swap;
Deprecations.outOfOrderArgs.warn(scriptEntry);
}
if (!scriptEntry.hasObject("players") && Utilities.entryHasPlayer(scriptEntry)) {
scriptEntry.defaultObject("players", Collections.singletonList(Utilities.getEntryPlayer(scriptEntry)));
if (players == null) {
if (!Utilities.entryHasPlayer(scriptEntry)) {
throw new InvalidArgumentsRuntimeException("Must specify an online player!");
}
players = List.of(Utilities.getEntryPlayer(scriptEntry));
}
if (!scriptEntry.hasObject("location") && !scriptEntry.hasObject("cancel")) {
throw new InvalidArgumentsException("Must specify a valid location!");
LocationTag location = locationObj == null ? null : locationObj.asType(LocationTag.class, scriptEntry.context);
EntityTag entity = entityObj.asType(EntityTag.class, scriptEntry.context);
if (entity == null) {
throw new InvalidArgumentsRuntimeException("Must specify a valid entity!");
}
if (!scriptEntry.hasObject("players")) {
throw new InvalidArgumentsException("Must have a valid, online player attached!");
if (location == null && !cancel) {
throw new InvalidArgumentsRuntimeException("Must specify a valid location!");
}
if (!scriptEntry.hasObject("entity")) {
throw new InvalidArgumentsException("Must specify a valid entity!");
if (vehicle != null && !vehicle.isValid()) {
throw new InvalidArgumentsRuntimeException("Must specify a valid entity to mount to!");
}
scriptEntry.defaultObject("duration", new DurationTag(10));
}

@Override
public void execute(ScriptEntry scriptEntry) {
EntityTag entity = scriptEntry.getObjectTag("entity");
LocationTag location = scriptEntry.getObjectTag("location");
List<PlayerTag> players = (List<PlayerTag>) scriptEntry.getObject("players");
DurationTag duration = scriptEntry.getObjectTag("duration");
ElementTag cancel = scriptEntry.getElement("cancel");
if (scriptEntry.dbCallShouldDebug()) {
Debug.report(scriptEntry, getName(), entity, cancel, location, duration, db("players", players));
if (!cancel) {
FakeEntity created = FakeEntity.showFakeEntityTo(players, entity, location, duration, vehicle);
scriptEntry.saveObject("faked_entity", created.entity);
return;
}
if (cancel != null && cancel.asBoolean()) {
if (entity.isFake) {
FakeEntity fakeEnt = FakeEntity.idsToEntities.get(entity.getUUID());
if (fakeEnt != null) {
fakeEnt.cancelEntity();
}
else {
Debug.echoDebug(scriptEntry, "Entity '" + entity + "' cannot be cancelled: not listed in fake-entity map.");
}
if (entity.isFake) {
FakeEntity fakeEnt = FakeEntity.idsToEntities.get(entity.getUUID());
if (fakeEnt != null) {
fakeEnt.cancelEntity();
}
else {
Debug.echoDebug(scriptEntry, "Entity '" + entity + "' cannot be cancelled: not listed in fake-entity map.");
}
}
else {
FakeEntity created = FakeEntity.showFakeEntityTo(players, entity, location, duration);
scriptEntry.saveObject("faked_entity", created.entity);
}
}
}
Expand Up @@ -51,9 +51,12 @@ public FakeEntity(List<PlayerTag> player, LocationTag location, int id) {
this.id = id;
}

public static FakeEntity showFakeEntityTo(List<PlayerTag> players, EntityTag typeToSpawn, LocationTag location, DurationTag duration) {
public static FakeEntity showFakeEntityTo(List<PlayerTag> players, EntityTag typeToSpawn, LocationTag location, DurationTag duration, EntityTag vehicle) {
NetworkInterceptHelper.enable();
FakeEntity fakeEntity = NMSHandler.playerHelper.sendEntitySpawn(players, typeToSpawn.getEntityType(), location, typeToSpawn.mechanisms == null ? null : new ArrayList<>(typeToSpawn.mechanisms), -1, null, true);
if (vehicle != null) {
NMSHandler.playerHelper.addFakePassenger(players, vehicle.getBukkitEntity(), fakeEntity);
}
idsToEntities.put(fakeEntity.overrideUUID == null ? fakeEntity.entity.getUUID() : fakeEntity.overrideUUID, fakeEntity);
for (PlayerTag player : players) {
UUID uuid = player.getPlayerEntity().getUniqueId();
Expand Down
Expand Up @@ -95,6 +95,9 @@ public class ReflectionMappingsInfo {
// net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket
public static String ClientboundSetEntityMotionPacket_id = "a";

// net.minecraft.network.protocol.game.ClientboundSetPassengersPacket
public static String ClientboundSetPassengersPacket_passengers = "b";

// net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket
public static String ClientboundTeleportEntityPacket_id = "a";
public static String ClientboundTeleportEntityPacket_x = "b";
Expand Down
Expand Up @@ -58,6 +58,7 @@
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R1.boss.CraftBossBar;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
Expand All @@ -76,6 +77,7 @@ public class PlayerHelperImpl extends PlayerHelper {

public static final Field FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundTickCount, int.class);
public static final Field VEHICLE_FLY_TICKS = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_aboveGroundVehicleTickCount, int.class);
public static final Field PASSENGERS_PACKET_PASSENGERS = ReflectionHelper.getFields(ClientboundSetPassengersPacket.class).get(ReflectionMappingsInfo.ClientboundSetPassengersPacket_passengers, int[].class);
public static final MethodHandle PLAYER_RESPAWNFORCED_SETTER = ReflectionHelper.getFinalSetter(ServerPlayer.class, ReflectionMappingsInfo.ServerPlayer_respawnForced, boolean.class);

public static final EntityDataAccessor<Byte> PLAYER_DATA_ACCESSOR_SKINLAYERS = ReflectionHelper.getFieldValue(net.minecraft.world.entity.player.Player.class, ReflectionMappingsInfo.Player_DATA_PLAYER_MODE_CUSTOMISATION, null);
Expand All @@ -101,6 +103,22 @@ public void deTrackEntity(Player player, Entity entity) {

public record TrackerData(PlayerTag player, ServerEntity tracker) {}

@Override
public void addFakePassenger(List<PlayerTag> players, Entity vehicle, FakeEntity fakePassenger) {
ClientboundSetPassengersPacket packet = new ClientboundSetPassengersPacket(((CraftEntity) vehicle).getHandle());
int[] newPassengers = Arrays.copyOf(packet.getPassengers(), packet.getPassengers().length + 1);
newPassengers[packet.getPassengers().length] = fakePassenger.id;
try {
PASSENGERS_PACKET_PASSENGERS.set(packet, newPassengers);
}
catch (IllegalAccessException e) {
Debug.echoError(e);
}
for (PlayerTag player : players) {
PacketHelperImpl.send(player.getPlayerEntity(), packet);
}
}

@Override
public FakeEntity sendEntitySpawn(List<PlayerTag> players, DenizenEntityType entityType, LocationTag location, ArrayList<Mechanism> mechanisms, int customId, UUID customUUID, boolean autoTrack) {
CraftWorld world = ((CraftWorld) location.getWorld());
Expand Down

0 comments on commit e2e883a

Please sign in to comment.