turn fakeplayer from a command into a clickgui module

FakePlayer now lives under Misc: enabling spawns the dummy at your
position with your equipment, disabling removes it, and it respawns
itself after a world change. Settings replace the old subcommands -
Name for its name, Swim for the swim pose (live-toggled), PopOnSpawn
for the totem event. Drops the .fakeplayer/.fp command.
This commit is contained in:
froslanz
2026-10-24 19:34:50 -04:00
parent 4c8a7c127e
commit 99b01ba903
4 changed files with 96 additions and 143 deletions
@@ -1,142 +0,0 @@
package cc.fros.features.commands.impl;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import cc.fros.features.commands.Command;
import cc.fros.manager.CommandManager;
import net.minecraft.client.player.RemotePlayer;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Pose;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
public class FakePlayerCommand extends Command {
private static final byte TOTEM_POP_EVENT_ID = 35;
private static final EquipmentSlot[] COPIED_SLOTS = {
EquipmentSlot.MAINHAND, EquipmentSlot.OFFHAND,
EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET
};
private final List<RemotePlayer> spawned = new ArrayList<>();
private int nextId = -13337;
public FakePlayerCommand() {
super("fakeplayer", "fp");
setDescription("Spawns a client-side fake player to test on");
}
@Override
public void createArgumentBuilder(LiteralArgumentBuilder<CommandManager> builder) {
builder.executes(ctx -> spawn("FakePlayer"))
.then(literal("remove")
.executes(ctx -> removeAll()))
.then(literal("pop")
.executes(ctx -> popAll()))
.then(literal("swim")
.executes(ctx -> toggleSwim()))
.then(argument("name", StringArgumentType.word())
.executes(ctx -> spawn(StringArgumentType.getString(ctx, "name"))));
}
private int popAll() {
if (nullCheck() || mc.getConnection() == null) {
return fail("You need to be in a world to do that.");
}
int popped = 0;
for (RemotePlayer fake : spawned) {
if (fake.level() != mc.level) continue;
mc.getConnection().handleEntityEvent(new ClientboundEntityEventPacket(fake, TOTEM_POP_EVENT_ID));
popped++;
}
if (popped == 0) {
return fail("No fake players to pop. Spawn one first.");
}
return success("Popped %s fake player(s)", popped);
}
private int toggleSwim() {
if (nullCheck()) {
return fail("You need to be in a world to do that.");
}
boolean anyStanding = false;
int found = 0;
for (RemotePlayer fake : spawned) {
if (fake.level() != mc.level) continue;
found++;
if (fake.getPose() != Pose.SWIMMING) anyStanding = true;
}
if (found == 0) {
return fail("No fake players. Spawn one first.");
}
Pose pose = anyStanding ? Pose.SWIMMING : Pose.STANDING;
for (RemotePlayer fake : spawned) {
if (fake.level() != mc.level) continue;
fake.setPose(pose);
fake.refreshDimensions();
}
return success(pose == Pose.SWIMMING
? "Fake player(s) now in swim pose."
: "Fake player(s) now standing.");
}
private int spawn(String name) {
if (nullCheck()) {
return fail("You need to be in a world to do that.");
}
int despawned = despawnAll();
GameProfile profile = new GameProfile(UUID.randomUUID(), name);
RemotePlayer fake = new RemotePlayer(mc.level, profile);
fake.setId(nextId--);
fake.copyPosition(mc.player);
fake.setYHeadRot(mc.player.getYHeadRot());
fake.setHealth(20f);
for (EquipmentSlot slot : COPIED_SLOTS) {
fake.setItemSlot(slot, mc.player.getItemBySlot(slot).copy());
}
mc.level.addEntity(fake);
spawned.add(fake);
if (despawned > 0) {
return success("Replaced fake player with {green} %s", name);
}
return success("Spawned fake player {green} %s", name);
}
private int removeAll() {
if (nullCheck()) {
spawned.clear();
return fail("You need to be in a world to do that.");
}
int removed = despawnAll();
if (removed == 0) {
return success("No fake players to remove.");
}
return success("Removed %s fake player(s)", removed);
}
private int despawnAll() {
int removed = 0;
Iterator<RemotePlayer> it = spawned.iterator();
while (it.hasNext()) {
RemotePlayer fake = it.next();
it.remove();
if (fake.level() != mc.level) continue;
mc.level.removeEntity(fake.getId(), Entity.RemovalReason.DISCARDED);
removed++;
}
return removed;
}
}
@@ -0,0 +1,94 @@
package cc.fros.features.modules.misc;
import cc.fros.features.modules.Module;
import cc.fros.features.settings.Setting;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.player.RemotePlayer;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Pose;
import java.util.UUID;
public class FakePlayerModule extends Module {
private static final byte TOTEM_POP_EVENT_ID = 35;
private static final EquipmentSlot[] COPIED_SLOTS = {
EquipmentSlot.MAINHAND, EquipmentSlot.OFFHAND,
EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET
};
private final Setting<String> name = str("Name", "FakePlayer");
private final Setting<Boolean> swim = bool("Swim", false);
private final Setting<Boolean> popOnSpawn = bool("PopOnSpawn", false);
private RemotePlayer fake;
private int nextId = -13337;
public FakePlayerModule() {
super("FakePlayer", "Spawns a client-side fake player to test on", Category.MISC);
}
@Override
public void onEnable() {
spawn();
}
@Override
public void onDisable() {
despawn();
}
@Override
public void onTick() {
if (nullCheck()) return;
// respawn after a world change or if anything removed it
if (fake == null || fake.level() != mc.level || fake.isRemoved()) {
spawn();
return;
}
// keep the pose in sync with the setting
Pose desired = swim.getValue() ? Pose.SWIMMING : Pose.STANDING;
if (fake.getPose() != desired) {
fake.setPose(desired);
fake.refreshDimensions();
}
}
private void spawn() {
if (nullCheck()) return;
despawn();
GameProfile profile = new GameProfile(UUID.randomUUID(), name.getValue());
fake = new RemotePlayer(mc.level, profile);
fake.setId(nextId--);
fake.copyPosition(mc.player);
fake.setYHeadRot(mc.player.getYHeadRot());
fake.setHealth(20f);
for (EquipmentSlot slot : COPIED_SLOTS) {
fake.setItemSlot(slot, mc.player.getItemBySlot(slot).copy());
}
if (swim.getValue()) {
fake.setPose(Pose.SWIMMING);
fake.refreshDimensions();
}
mc.level.addEntity(fake);
if (popOnSpawn.getValue() && mc.getConnection() != null) {
mc.getConnection().handleEntityEvent(new ClientboundEntityEventPacket(fake, TOTEM_POP_EVENT_ID));
}
}
private void despawn() {
if (fake == null) return;
if (mc.level != null && fake.level() == mc.level) {
mc.level.removeEntity(fake.getId(), Entity.RemovalReason.DISCARDED);
}
fake = null;
}
}
@@ -35,7 +35,6 @@ public class CommandManager extends Feature implements Jsonable {
public void init() {
register(new BindCommand());
register(new DrawnCommand());
register(new FakePlayerCommand());
register(new FriendCommand());
register(new HelpCommand());
register(new PrefixCommand());
@@ -29,6 +29,7 @@ import cc.fros.features.modules.hud.RadarHudModule;
import cc.fros.features.modules.hud.TimeHudModule;
import cc.fros.features.modules.hud.WelcomerHudModule;
import cc.fros.features.modules.hud.WatermarkHudModule;
import cc.fros.features.modules.misc.FakePlayerModule;
import cc.fros.features.modules.misc.MCFModule;
import cc.fros.features.modules.movement.FastAccelModule;
import cc.fros.features.modules.movement.ReverseStepModule;
@@ -70,6 +71,7 @@ public class ModuleManager implements Jsonable, Util {
register(new NotificationsModule());
register(new CriticalsModule());
register(new MCFModule());
register(new FakePlayerModule());
register(new StepModule());
register(new ReverseStepModule());
register(new FastPlaceModule());