ChunkTintCommand.java
package com.hypixel.hytale.server.core.command.commands.world.chunk;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.codecs.EnumCodec;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.CommandManager;
import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg;
import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.ui.builder.EventData;
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor;
import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import javax.annotation.Nonnull;
public class ChunkTintCommand extends AbstractPlayerCommand {
private static final int BLUR_RADIUS = 5;
private static final double BLUR_SIGMA = 1.5;
@Nonnull
private static final Message MESSAGE_GENERAL_CHUNK_NOT_LOADED = Message.translation("server.general.chunkNotLoaded");
@Nonnull
private static final Message MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS = Message.translation("server.commands.chunk.tint.success");
@Nonnull
private static final Message MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS_BLUR = Message.translation("server.commands.chunk.tint.success.blur");
@Nonnull
private final RequiredArg<Integer> colorArg;
@Nonnull
private final DefaultArg<Integer> radiusArg;
@Nonnull
private final DefaultArg<Double> sigmaArg;
@Nonnull
private final FlagArg blurArg;
public ChunkTintCommand() {
super("tint", "server.commands.chunk.tint.desc");
this.colorArg = this.withRequiredArg("color", "server.commands.chunk.tint.color.desc", ArgTypes.COLOR);
this.radiusArg = (DefaultArg)this.withDefaultArg("radius", "server.commands.chunk.tint.radius.desc", ArgTypes.INTEGER, 5, "server.commands.chunk.tint.radius.default").addValidator(Validators.greaterThan(0));
this.sigmaArg = (DefaultArg)this.withDefaultArg("sigma", "server.commands.chunk.tint.sigma.desc", ArgTypes.DOUBLE, 1.5, "server.commands.chunk.tint.sigma.default").addValidator(Validators.greaterThan(0.0));
this.blurArg = this.withFlagArg("blur", "server.commands.chunk.tint.blur.desc");
this.addUsageVariant(new TintChunkPageCommand());
}
protected void execute(@Nonnull CommandContext context, @Nonnull Store<EntityStore> store, @Nonnull Ref<EntityStore> ref, @Nonnull PlayerRef playerRef, @Nonnull World world) {
int color = (Integer)this.colorArg.get(context);
TransformComponent transformComponent = (TransformComponent)store.getComponent(ref, TransformComponent.getComponentType());
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
int chunkX = MathUtil.floor(position.getX()) >> 5;
int chunkZ = MathUtil.floor(position.getZ()) >> 5;
ChunkStore chunkStore = world.getChunkStore();
Store<ChunkStore> chunkStoreStore = chunkStore.getStore();
LongOpenHashSet updateChunks = new LongOpenHashSet();
int radius = 0;
double sigma = 0.0;
long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ);
Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
if (chunkRef != null && chunkRef.isValid()) {
BlockChunk blockChunk = (BlockChunk)chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType());
if (blockChunk != null) {
for(int x = 0; x < 32; ++x) {
for(int z = 0; z < 32; ++z) {
blockChunk.setTint(x, z, color);
}
}
updateChunks.add(chunkIndex);
}
if (this.blurArg.provided(context)) {
radius = (Integer)this.radiusArg.get(context);
sigma = (Double)this.sigmaArg.get(context);
double[] matrix = gaussianMatrix(sigma, radius);
int blockX = chunkX << 5;
int blockZ = chunkZ << 5;
Long2IntOpenHashMap newTintMap = new Long2IntOpenHashMap();
LocalCachedChunkAccessor accessor = LocalCachedChunkAccessor.atWorldCoords(world, blockX, blockZ, 32 + radius * 2);
for(int x = -radius; x <= 32 + radius; ++x) {
for(int z = -radius; z <= 32 + radius; ++z) {
int offsetX = blockX + x;
int offsetZ = blockZ + z;
int blurred = blur(accessor, radius, matrix, offsetX, offsetZ);
newTintMap.put(MathUtil.packLong(offsetX, offsetZ), blurred);
}
}
for(Long2IntMap.Entry entry : newTintMap.long2IntEntrySet()) {
long key = entry.getLongKey();
int x = MathUtil.unpackLeft(key);
int z = MathUtil.unpackRight(key);
long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z);
Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
if (chunkRef != null && chunkRef.isValid()) {
BlockChunk blockChunk = (BlockChunk)chunkStoreStore.getComponent(chunkRef, BlockChunk.getComponentType());
if (blockChunk != null) {
blockChunk.setTint(x, z, entry.getIntValue());
updateChunks.add(chunkIndex);
}
}
}
}
updateChunks.forEach((chunkIndexx) -> world.getNotificationHandler().updateChunk(chunkIndexx));
if (this.blurArg.provided(context)) {
context.sendMessage(MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS_BLUR.param("chunkX", chunkX).param("chunkZ", chunkZ).param("chunksAffected", updateChunks.size()).param("radius", radius).param("sigma", sigma));
} else {
context.sendMessage(MESSAGE_COMMANDS_CHUNK_TINT_SUCCESS.param("chunkX", chunkX).param("chunkZ", chunkZ));
}
} else {
context.sendMessage(MESSAGE_GENERAL_CHUNK_NOT_LOADED.param("chunkX", chunkX).param("chunkZ", chunkZ));
}
}
private static int blur(@Nonnull ChunkAccessor<WorldChunk> chunkAccessor, int radius, double[] matrix, int x, int z) {
double r = 0.0;
double g = 0.0;
double b = 0.0;
for(int ix = -radius; ix <= radius; ++ix) {
for(int iz = -radius; iz <= radius; ++iz) {
double factor = matrix[gaussianIndex(radius, ix, iz)];
int ax = x + ix;
int az = z + iz;
WorldChunk worldChunk = (WorldChunk)chunkAccessor.getChunk(ChunkUtil.indexChunkFromBlock(ax, az));
if (worldChunk != null) {
BlockChunk blockChunk = worldChunk.getBlockChunk();
if (blockChunk != null) {
int c = blockChunk.getTint(ax, az);
r += (double)(c >> 16 & 255) * factor;
g += (double)(c >> 8 & 255) * factor;
b += (double)(c & 255) * factor;
}
}
}
}
return -16777216 | MathUtil.floor(r) << 16 | MathUtil.floor(g) << 8 | MathUtil.floor(b);
}
private static double gaussian2d(double sigma, double x, double y) {
return 1.0 / (6.283185307179586 * sigma * sigma) * Math.pow(2.718281828459045, -(x * x + y * y) / (2.0 * sigma * sigma));
}
private static double[] gaussianMatrix(double sigma, int radius) {
int length = 2 * radius + 1;
double[] matrix = new double[length * length];
for(int x = -radius; x <= radius; ++x) {
for(int y = -radius; y <= radius; ++y) {
double value = gaussian2d(sigma, (double)x, (double)y);
matrix[gaussianIndex(radius, x, y)] = value;
}
}
double sum = 0.0;
for(double val : matrix) {
sum += val;
}
for(int i = 0; i < matrix.length; ++i) {
matrix[i] /= sum;
}
return matrix;
}
private static int gaussianIndex(int radius, int x, int y) {
x += radius;
y += radius;
return x * (2 * radius + 1) + y;
}
static class TintChunkPageCommand extends AbstractPlayerCommand {
TintChunkPageCommand() {
super("server.commands.chunk.tint.get");
}
protected void execute(@Nonnull CommandContext context, @Nonnull Store<EntityStore> store, @Nonnull Ref<EntityStore> ref, @Nonnull PlayerRef playerRef, @Nonnull World world) {
Player playerComponent = (Player)store.getComponent(ref, Player.getComponentType());
assert playerComponent != null;
playerComponent.getPageManager().openCustomPage(ref, store, new TintChunkPage(playerRef));
}
}
public static class TintChunkPage extends InteractiveCustomUIPage<TintChunkPageEventData> {
TintChunkPage(@Nonnull PlayerRef playerRef) {
super(playerRef, CustomPageLifetime.CanDismiss, ChunkTintCommand.TintChunkPage.TintChunkPageEventData.CODEC);
}
public void build(@Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store) {
commandBuilder.append("Pages/TintChunkPage.ui");
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ColorPicker", (new EventData()).append("@Color", "#ColorPicker.Value").append("Submit", ChunkTintCommand.TintChunkPage.TintChunkPageAction.ColorChanged.name()), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ApplyButton", (new EventData()).append("@Color", "#ColorPicker.Value").append("@Radius", "#Radius.Value").append("@BlurEnabled", "#BlurEnabledContainer #CheckBox.Value").append("@Sigma", "#Sigma.Value").append("@HexColor", "#HexColor.Value").append("Submit", ChunkTintCommand.TintChunkPage.TintChunkPageAction.Submit.name()), false);
}
public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull TintChunkPageEventData data) {
switch (data.getAction().ordinal()) {
case 0:
String color = data.getColor().substring(0, 7);
int radiusStr = data.getRadius();
int sigmaStr = data.getSigma();
if (!data.getKeyHexColor().isEmpty()) {
color = data.getKeyHexColor();
if (!color.startsWith("#")) {
color = "#" + color;
}
}
if (data.isBlurEnabled()) {
CommandManager.get().handleCommand(this.playerRef, "chunk tint " + color + " --blur --radius=" + radiusStr + " --sigma=" + sigmaStr);
} else {
CommandManager.get().handleCommand(this.playerRef, "chunk tint " + color);
}
break;
case 1:
String color = data.getColor().substring(0, 7);
UICommandBuilder commands = new UICommandBuilder();
commands.set("#HexColor.Value", color);
this.sendUpdate(commands);
}
}
public static enum TintChunkPageAction {
Submit,
ColorChanged;
private TintChunkPageAction() {
}
}
public static class TintChunkPageEventData {
public static final String KEY_COLOR = "@Color";
public static final String KEY_RADIUS = "@Radius";
public static final String KEY_SIGMA = "@Sigma";
public static final String KEY_BLUR_ENABLED = "@BlurEnabled";
public static final String KEY_HEX_COLOR = "@HexColor";
public static final String KEY_ACTION = "Submit";
@Nonnull
public static BuilderCodec<TintChunkPageEventData> CODEC;
private String color;
private int radius;
private int sigma;
private String hexColor;
private boolean isBlurEnabled;
private TintChunkPageAction action;
public TintChunkPageEventData() {
}
public String getColor() {
return this.color;
}
public int getRadius() {
return this.radius;
}
public int getSigma() {
return this.sigma;
}
public boolean isBlurEnabled() {
return this.isBlurEnabled;
}
public String getKeyHexColor() {
return this.hexColor;
}
public TintChunkPageAction getAction() {
return this.action;
}
static {
CODEC = ((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)((BuilderCodec.Builder)BuilderCodec.builder(TintChunkPageEventData.class, TintChunkPageEventData::new).addField(new KeyedCodec("@Color", Codec.STRING), (entry, s) -> entry.color = s, (entry) -> entry.color)).addField(new KeyedCodec("@Radius", Codec.INTEGER), (entry, s) -> entry.radius = s, (entry) -> entry.radius)).addField(new KeyedCodec("@Sigma", Codec.INTEGER), (entry, s) -> entry.sigma = s, (entry) -> entry.sigma)).addField(new KeyedCodec("@BlurEnabled", Codec.BOOLEAN), (entry, b) -> entry.isBlurEnabled = b, (entry) -> entry.isBlurEnabled)).addField(new KeyedCodec("@HexColor", Codec.STRING), (entry, s) -> entry.hexColor = s, (entry) -> entry.hexColor)).addField(new KeyedCodec("Submit", new EnumCodec(TintChunkPageAction.class)), (entry, s) -> entry.action = s, (entry) -> entry.action)).build();
}
}
}
}