classpublicPriority 3
Bot
com.hypixel.hytale.server.core.command.commands.debug.stresstest.Bot
extends SimpleChannelInboundHandler
11
Methods
11
Public Methods
6
Fields
1
Constructors
Constants
Asset[]EMPTY_ASSET_ARRAY= new Asset[0]
ScheduledExecutorServiceEXECUTOR= Executors.newScheduledThreadPool(8)
EventLoopGroupWORKER_GROUP= NettyUtil.getEventLoopGroup(8, "BotWorkerGroup")
Constructors
public
Bot(String name, BotConfig config, int tickStepNanos)throws InterruptedException, SocketException
Methods
Public Methods (11)
public
void channelActive(ChannelHandlerContext ctx)public
void channelInactive(ChannelHandlerContext ctx)public
void channelRead0(ChannelHandlerContext ctx, Packet packet)public
ClientMovement createMovementPacket()@Nonnull
public
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)publicstatic
EntityUpdate findEntityUpdate(EntityUpdates bulkList, int id)@Nullable
public
void shutdown()public
void tick(float dt)public
String toString()@Nonnull
public
void updateModelTransform(ModelTransform modelTransform)public
void updateRotation(Direction lookOrientation)Fields
Private/Package Fields (6)
private
SocketChannel channelprivate
BotConfig configprivate
int idprivate
HytaleLogger loggerprivate
String nameprivate
Vector3d posInheritance
Parent
Current
Interface
Child
Use mouse wheel to zoom, drag to pan. Click nodes to navigate.
Related Classes
Used By
HytaleLoggerVector3dVector3fAssetComponentUpdateComponentUpdateTypeDirectionEntityUpdateInstantDataModelTransformMovementStatesPacketPositionTeleportAckPacketDecoderPacketEncoderClientTypeConnectDisconnectDisconnectTypePingPongPongTypeEntityUpdatesClientMovementClientReadyClientTeleportSetClientIdPlayerOptionsRequestAssetsViewRadiusCosmeticsModuleServerManagerNettyUtilWorldTimeResourcePositionUtil
Source Code
package com.hypixel.hytale.server.core.command.commands.debug.stresstest;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.protocol.Asset;
import com.hypixel.hytale.protocol.ComponentUpdate;
import com.hypixel.hytale.protocol.ComponentUpdateType;
import com.hypixel.hytale.protocol.Direction;
import com.hypixel.hytale.protocol.EntityUpdate;
import com.hypixel.hytale.protocol.InstantData;
import com.hypixel.hytale.protocol.ModelTransform;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.protocol.Position;
import com.hypixel.hytale.protocol.TeleportAck;
import com.hypixel.hytale.protocol.io.netty.PacketDecoder;
import com.hypixel.hytale.protocol.io.netty.PacketEncoder;
import com.hypixel.hytale.protocol.packets.connection.ClientType;
import com.hypixel.hytale.protocol.packets.connection.Connect;
import com.hypixel.hytale.protocol.packets.connection.Disconnect;
import com.hypixel.hytale.protocol.packets.connection.DisconnectType;
import com.hypixel.hytale.protocol.packets.connection.Ping;
import com.hypixel.hytale.protocol.packets.connection.Pong;
import com.hypixel.hytale.protocol.packets.connection.PongType;
import com.hypixel.hytale.protocol.packets.entities.EntityUpdates;
import com.hypixel.hytale.protocol.packets.player.ClientMovement;
import com.hypixel.hytale.protocol.packets.player.ClientReady;
import com.hypixel.hytale.protocol.packets.player.ClientTeleport;
import com.hypixel.hytale.protocol.packets.player.SetClientId;
import com.hypixel.hytale.protocol.packets.setup.PlayerOptions;
import com.hypixel.hytale.protocol.packets.setup.RequestAssets;
import com.hypixel.hytale.protocol.packets.setup.ViewRadius;
import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
import com.hypixel.hytale.server.core.io.ServerManager;
import com.hypixel.hytale.server.core.io.netty.NettyUtil;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.server.core.util.PositionUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class Bot extends SimpleChannelInboundHandler<Packet> {
private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(8);
private static final EventLoopGroup WORKER_GROUP = NettyUtil.getEventLoopGroup(8, "BotWorkerGroup");
public static final Asset[] EMPTY_ASSET_ARRAY = new Asset[0];
@Nonnull
private final HytaleLogger logger;
private final String name;
@Nonnull
private final BotConfig config;
@Nonnull
private final ScheduledFuture<?> tickFuture;
private final ObjectArrayFIFOQueue<Ping> pingPackets = new ObjectArrayFIFOQueue();
private final MovementStates movementStates = new MovementStates();
@Nullable
private SocketChannel channel;
private int id = -1;
private Vector3d pos;
private final Vector3f rotation = new Vector3f();
private final Vector3d destination = new Vector3d();
private final Vector3d temp = new Vector3d();
private final Vector3f targetRotation = new Vector3f();
public Bot(String name, @Nonnull BotConfig config, int tickStepNanos) throws InterruptedException, SocketException {
this.logger = HytaleLogger.get(name);
this.name = name;
this.config = config;
this.destination.assign(config.spawn.getPosition());
this.destination.y = ThreadLocalRandom.current().nextDouble(config.flyYHeight.getX(), config.flyYHeight.getY());
InetSocketAddress address = ServerManager.get().getLocalOrPublicAddress();
this.logger.at(Level.INFO).log("Booting Bot! Connecting to %s", address);
((Bootstrap)((Bootstrap)((Bootstrap)((Bootstrap)new Bootstrap().group(WORKER_GROUP))
.channel(Epoll.isAvailable() ? EpollSocketChannel.class : (KQueue.isAvailable() ? KQueueSocketChannel.class : NioSocketChannel.class)))
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE))
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(@Nonnull SocketChannel channel) {
Bot.this.channel = channel;
channel.pipeline().addLast("packetDecoder", new PacketDecoder());
channel.pipeline().addLast("packetEncoder", new PacketEncoder());
if (NettyUtil.PACKET_LOGGER.getLevel() != Level.OFF) {
channel.pipeline().addLast("logger", NettyUtil.LOGGER);
}
channel.pipeline().addLast("handler", Bot.this);
}
}))
.connect(address)
.sync();
float dt = (float)((double)tickStepNanos / 1.0E9);
this.tickFuture = EXECUTOR.scheduleAtFixedRate(() -> {
if (this.channel != null) {
try {
this.tick(dt);
} catch (Throwable var4x) {
((HytaleLogger.Api)this.logger.at(Level.SEVERE).withCause(var4x)).log("Exception ticking %s", name);
}
}
}, (long)tickStepNanos, (long)tickStepNanos, TimeUnit.NANOSECONDS);
}
public void shutdown() {
this.tickFuture.cancel(false);
if (this.channel != null && !this.channel.isShutdown()) {
try {
this.channel.shutdown().await(1L, TimeUnit.SECONDS);
this.channel = null;
} catch (InterruptedException var2) {
var2.printStackTrace();
}
}
}
public void tick(float dt) {
while (!this.pingPackets.isEmpty()) {
Ping packet = (Ping)this.pingPackets.dequeue();
this.channel.write(new Pong(packet.id, WorldTimeResource.instantToInstantData(Instant.now()), PongType.Tick, (short)0));
}
if (this.pos == null) {
this.channel.flush();
} else {
double movementDistance = this.config.flySpeed * (double)dt;
if (this.pos.distanceSquaredTo(this.destination) <= movementDistance * movementDistance) {
ThreadLocalRandom random = ThreadLocalRandom.current();
double randX = random.nextDouble(-this.config.radius, this.config.radius);
double randY = random.nextDouble(this.config.flyYHeight.getX(), this.config.flyYHeight.getY());
double randZ = random.nextDouble(-this.config.radius, this.config.radius);
this.destination.assign(this.config.spawn.getPosition());
this.destination.y = randY;
this.destination.add(randX, 0.0, randZ);
}
this.temp.assign(this.destination).subtract(this.pos);
Vector3f.lookAt(this.temp, this.targetRotation);
Vector3f.lerpAngle(this.rotation, this.targetRotation, 0.3F, this.rotation);
this.temp.normalize();
this.temp.scale(movementDistance);
this.pos.add(this.temp);
this.movementStates.flying = true;
this.channel.writeAndFlush(this.createMovementPacket());
}
}
public void channelActive(@Nonnull ChannelHandlerContext ctx) {
UUID uuid = UUID.nameUUIDFromBytes(("BOT|" + this.name).getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(
new Connect("6708f121966c1c443f4b0eb525b2f81d0a8dc61f5003a692a8fa157e5e02cea9", ClientType.Game, "en", null, uuid, this.name, null, null)
);
this.logger.at(Level.INFO).log("Connected!");
}
public void channelInactive(ChannelHandlerContext ctx) {
this.logger.at(Level.INFO).log("Disconnected!");
this.shutdown();
StressTestStartCommand.BOTS.remove(this);
}
public void exceptionCaught(@Nonnull ChannelHandlerContext ctx, @Nonnull Throwable cause) {
((HytaleLogger.Api)this.logger.at(Level.WARNING).withCause(cause)).log("Got exception from netty pipeline");
if (ctx.channel().isWritable()) {
ctx.channel().writeAndFlush(new Disconnect(cause.getMessage(), DisconnectType.Crash)).addListener(ChannelFutureListener.CLOSE);
} else {
ctx.channel().close();
}
this.shutdown();
StressTestStartCommand.BOTS.remove(this);
}
public void channelRead0(@Nonnull ChannelHandlerContext ctx, @Nonnull Packet packet) {
switch (packet.getId()) {
case 1:
this.logger.at(Level.INFO).log("Disconnected for: %s %s", ((Disconnect)packet).reason, ((Disconnect)packet).type);
ctx.close();
break;
case 2:
Ping ping = (Ping)packet;
InstantData instantData = WorldTimeResource.instantToInstantData(Instant.now());
ctx.write(new Pong(ping.id, instantData, PongType.Raw, (short)0));
ctx.writeAndFlush(new Pong(ping.id, instantData, PongType.Direct, (short)0));
this.pingPackets.enqueue(ping);
break;
case 20:
ctx.write(new RequestAssets(EMPTY_ASSET_ARRAY));
ctx.write(new ViewRadius(this.config.viewRadius));
ctx.writeAndFlush(new PlayerOptions(CosmeticsModule.get().generateRandomSkin(ThreadLocalRandom.current())));
break;
case 100:
this.id = ((SetClientId)packet).clientId;
break;
case 104:
ctx.writeAndFlush(new ClientReady(true, this.id != -1));
break;
case 109:
ClientTeleport clientTeleport = (ClientTeleport)packet;
ModelTransform modelTransform = clientTeleport.modelTransform;
if (modelTransform == null) {
return;
}
this.updateModelTransform(modelTransform);
this.logger.at(Level.INFO).log("TP: %s (sending ack for teleportId: %s)", this.pos, clientTeleport.teleportId);
ClientMovement movement = this.createMovementPacket();
movement.teleportAck = new TeleportAck(clientTeleport.teleportId);
ctx.writeAndFlush(movement);
break;
case 161:
EntityUpdates entityUpdates = (EntityUpdates)packet;
EntityUpdate entry = findEntityUpdate(entityUpdates, this.id);
if (entry == null) {
return;
}
for (ComponentUpdate update : entry.updates) {
if (update.type == ComponentUpdateType.Transform) {
this.updateModelTransform(update.transform);
break;
}
}
}
}
public void updateModelTransform(@Nonnull ModelTransform modelTransform) {
Position position = modelTransform.position;
if (position != null) {
if (this.pos == null) {
this.pos = new Vector3d();
}
this.pos.assign(position.x, position.y, position.z);
}
Direction lookOrientation = modelTransform.lookOrientation;
if (lookOrientation != null) {
this.updateRotation(lookOrientation);
}
}
public void updateRotation(@Nonnull Direction lookOrientation) {
if (!Float.isNaN(lookOrientation.yaw)) {
this.rotation.setYaw(lookOrientation.yaw);
}
if (!Float.isNaN(lookOrientation.pitch)) {
this.rotation.setPitch(lookOrientation.pitch);
}
if (!Float.isNaN(lookOrientation.roll)) {
this.rotation.setRoll(lookOrientation.roll);
}
}
@Nonnull
public ClientMovement createMovementPacket() {
ClientMovement movement = new ClientMovement();
movement.absolutePosition = PositionUtil.toPositionPacket(this.pos);
movement.lookOrientation = PositionUtil.toDirectionPacket(this.rotation);
movement.bodyOrientation = PositionUtil.toDirectionPacket(this.rotation);
movement.bodyOrientation.pitch = 0.0F;
movement.movementStates = this.movementStates;
return movement;
}
@Nonnull
public String toString() {
return "Bot{name='" + this.name + "', id=" + this.id + "}";
}
@Nullable
public static EntityUpdate findEntityUpdate(@Nonnull EntityUpdates bulkList, int id) {
if (bulkList.updates == null) {
return null;
} else {
for (EntityUpdate otherEntry : bulkList.updates) {
if (otherEntry.networkId == id) {
return otherEntry;
}
}
return null;
}
}
}