Merge branch 'master' into 1.16
# Conflicts: # src/main/java/carpet/utils/Messenger.java
This commit is contained in:
commit
1c46393c4d
27 files changed
+1204
-156
No files matched your search
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import carpet.commands.*;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.logging.LoggerRegistry;
|
||||
import carpet.script.CarpetScriptServer;
|
||||
@@ -60,6 +61,7 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
scriptServer.loadAllWorldScripts();
|
||||
MobAI.resetTrackers();
|
||||
LoggerRegistry.initLoggers();
|
||||
//TickSpeed.reset();
|
||||
}
|
||||
|
||||
public static void tick(MinecraftServer server)
|
||||
@@ -102,30 +104,41 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
|
||||
public static void onPlayerLoggedIn(ServerPlayerEntity player)
|
||||
{
|
||||
ServerNetworkHandler.onPlayerJoin(player);
|
||||
LoggerRegistry.playerConnected(player);
|
||||
extensions.forEach(e -> e.onPlayerLoggedIn(player));
|
||||
|
||||
}
|
||||
|
||||
public static void onPlayerLoggedOut(ServerPlayerEntity player)
|
||||
{
|
||||
ServerNetworkHandler.onPlayerLoggedOut(player);
|
||||
LoggerRegistry.playerDisconnected(player);
|
||||
extensions.forEach(e -> e.onPlayerLoggedOut(player));
|
||||
}
|
||||
|
||||
public static void onServerClosed(MinecraftServer server)
|
||||
{
|
||||
ServerNetworkHandler.close();
|
||||
currentCommandDispatcher = null;
|
||||
if (scriptServer != null) scriptServer.onClose();
|
||||
settingsManager.detachServer();
|
||||
|
||||
LoggerRegistry.stopLoggers();
|
||||
TickSpeed.reset();
|
||||
extensions.forEach(e -> e.onServerClosed(server));
|
||||
minecraft_server = null;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
public static void registerExtensionLoggers()
|
||||
{
|
||||
extensions.forEach(CarpetExtension::registerLoggers);
|
||||
}
|
||||
|
||||
public static void disconnect()
|
||||
{
|
||||
// this for whatever reason gets called multiple times even when joining;
|
||||
TickSpeed.reset();
|
||||
settingsManager.detachServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class CarpetSettings
|
||||
|
||||
private static class LanguageValidator extends Validator<String> {
|
||||
@Override public String validate(ServerCommandSource source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
if (currentRule.get().equals(newValue))
|
||||
if (currentRule.get().equals(newValue) || source == null)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -484,7 +484,7 @@ public class CarpetSettings
|
||||
{
|
||||
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string)
|
||||
{
|
||||
if (currentRule.get().equals(newValue))
|
||||
if (currentRule.get().equals(newValue) || source == null)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -535,6 +535,7 @@ public class CarpetSettings
|
||||
}
|
||||
}
|
||||
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
if (source == null) return newValue;
|
||||
if (newValue < 0 || newValue > 32)
|
||||
{
|
||||
Messenger.m(source, "r spawn chunk size has to be between 0 and 32");
|
||||
|
||||
@@ -67,7 +67,7 @@ public class TickCommand
|
||||
|
||||
private static int setTps(ServerCommandSource source, float tps)
|
||||
{
|
||||
TickSpeed.tickrate(tps);
|
||||
TickSpeed.tickrate(tps, true);
|
||||
queryTps(source);
|
||||
return (int)tps;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package carpet.fakes;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.PacketByteBuf;
|
||||
|
||||
public interface CustomPayloadC2SPacketInterface
|
||||
{
|
||||
Identifier getPacketChannel();
|
||||
PacketByteBuf getPacketData();
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.utils.Messenger;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -176,8 +177,9 @@ public class TickSpeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tickrate(float rate)
|
||||
//unused - mod compat reasons
|
||||
public static void tickrate(float rate) {tickrate(rate, true);}
|
||||
public static void tickrate(float rate, boolean update)
|
||||
{
|
||||
tickrate = rate;
|
||||
long mspt = (long)(1000.0 / tickrate);
|
||||
@@ -189,7 +191,7 @@ public class TickSpeed
|
||||
|
||||
TickSpeed.mspt = (float)mspt;
|
||||
|
||||
notifyTickrateListeners("carpet");
|
||||
if (update) notifyTickrateListeners("carpet");
|
||||
}
|
||||
|
||||
private static void tickrateChanged(String modId, float rate)
|
||||
@@ -221,6 +223,7 @@ public class TickSpeed
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerNetworkHandler.updateTickSpeedToConnectedPlayers();
|
||||
}
|
||||
|
||||
public static BiConsumer<String, Float> addTickrateListener(String modId, BiConsumer<String, Float> tickrateListener)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.network.CarpetClient;
|
||||
import carpet.network.ClientNetworkHandler;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
|
||||
import net.minecraft.text.Text;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ClientPlayNetworkHandler.class)
|
||||
public abstract class ClientPlayNetworkHandlerMixin
|
||||
{
|
||||
@Shadow private MinecraftClient client;
|
||||
|
||||
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
|
||||
private void onOnCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci)
|
||||
{
|
||||
if (CarpetClient.CARPET_CHANNEL.equals(packet.getChannel()))
|
||||
{
|
||||
ClientNetworkHandler.handleData(packet.getData(), client.player);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onGameJoin", at = @At("RETURN"))
|
||||
private void onGameJoined(GameJoinS2CPacket packet, CallbackInfo info)
|
||||
{
|
||||
CarpetClient.gameJoined(client.player);
|
||||
}
|
||||
|
||||
@Inject(method = "onDisconnected", at = @At("HEAD"))
|
||||
private void onCMDisconnected(Text reason, CallbackInfo ci)
|
||||
{
|
||||
CarpetClient.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.network.CarpetClient;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -13,8 +14,10 @@ public class ClientPlayerEntity_clientCommandMixin
|
||||
@Inject(method = "sendChatMessage", at = @At("HEAD"))
|
||||
private void inspectMessage(String string, CallbackInfo ci)
|
||||
{
|
||||
ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this;
|
||||
if (CarpetServer.minecraft_server == null)
|
||||
if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet())
|
||||
{
|
||||
ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this;
|
||||
CarpetServer.settingsManager.inspectClientsideCommand(playerSource.getCommandSource(), string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.fakes.CustomPayloadC2SPacketInterface;
|
||||
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.PacketByteBuf;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(CustomPayloadC2SPacket.class)
|
||||
public class CustomPayloadC2SPacketMixin implements CustomPayloadC2SPacketInterface
|
||||
{
|
||||
@Shadow
|
||||
private Identifier channel;
|
||||
@Shadow
|
||||
private PacketByteBuf data;
|
||||
|
||||
@Override
|
||||
public Identifier getPacketChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketByteBuf getPacketData() {
|
||||
return new PacketByteBuf(this.data.copy());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.network.CarpetClient;
|
||||
import carpet.script.utils.ShapesRenderer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.render.debug.DebugRenderer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(DebugRenderer.class)
|
||||
public class DebugRenderer_scarpetRenderMixin
|
||||
{
|
||||
private ShapesRenderer shapes;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void addRenderers(MinecraftClient client, CallbackInfo ci)
|
||||
{
|
||||
shapes = new ShapesRenderer(client);
|
||||
CarpetClient.shapes = shapes;
|
||||
}
|
||||
|
||||
@Inject(method = "reset", at = @At("HEAD"))
|
||||
private void resetScarpetRenderes(CallbackInfo ci)
|
||||
{
|
||||
shapes.reset();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void renderScarpetThings(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci)
|
||||
{
|
||||
shapes.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.network.CarpetClient;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
@@ -18,4 +20,10 @@ public class MinecraftClientMixin
|
||||
//CM start game hook
|
||||
CarpetServer.onGameStarted();
|
||||
}
|
||||
|
||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("HEAD"))
|
||||
private void onCloseGame(Screen screen, CallbackInfo ci)
|
||||
{
|
||||
CarpetClient.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.fakes.CustomPayloadC2SPacketInterface;
|
||||
import carpet.network.CarpetClient;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ServerPlayNetworkHandler.class)
|
||||
public class ServerPlayNetworkHandlerMixin
|
||||
{
|
||||
@Shadow public ServerPlayerEntity player;
|
||||
|
||||
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
|
||||
private void onCustomCarpetPayload(CustomPayloadC2SPacket packet, CallbackInfo ci)
|
||||
{
|
||||
Identifier channel = ((CustomPayloadC2SPacketInterface) packet).getPacketChannel();
|
||||
if (CarpetClient.CARPET_CHANNEL.equals(channel))
|
||||
{
|
||||
ServerNetworkHandler.handleData(((CustomPayloadC2SPacketInterface) packet).getPacketData(), player);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package carpet.network;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.script.utils.ShapesRenderer;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public class CarpetClient
|
||||
{
|
||||
public static final Object sync = new Object();
|
||||
public static final int HI = 69;
|
||||
public static final int HELLO = 420;
|
||||
public static final int DATA = 1;
|
||||
public static ShapesRenderer shapes = null;
|
||||
|
||||
private static ClientPlayerEntity clientPlayer = null;
|
||||
private static boolean isServerCarpet = false;
|
||||
public static String serverCarpetVersion;
|
||||
public static final Identifier CARPET_CHANNEL = new Identifier("carpet:hello");
|
||||
|
||||
public static void gameJoined(ClientPlayerEntity player)
|
||||
{
|
||||
synchronized (sync)
|
||||
{
|
||||
clientPlayer = player;
|
||||
// client didn't say hi back yet
|
||||
if (isServerCarpet)
|
||||
ClientNetworkHandler.respondHello();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void disconnect()
|
||||
{
|
||||
isServerCarpet = false;
|
||||
clientPlayer = null;
|
||||
CarpetServer.disconnect();
|
||||
}
|
||||
|
||||
public static void setCarpet()
|
||||
{
|
||||
isServerCarpet = true;
|
||||
}
|
||||
|
||||
public static ClientPlayerEntity getPlayer()
|
||||
{
|
||||
return clientPlayer;
|
||||
}
|
||||
|
||||
public static boolean isCarpet()
|
||||
{
|
||||
return isServerCarpet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package carpet.network;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.settings.ParsedRule;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.nbt.AbstractNumberTag;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
|
||||
import net.minecraft.util.PacketByteBuf;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ClientNetworkHandler
|
||||
{
|
||||
private static Map<String, BiConsumer<ClientPlayerEntity, Tag>> dataHandlers = new HashMap<String, BiConsumer<ClientPlayerEntity, Tag>>(){{
|
||||
put("Rules", (p, t) -> {
|
||||
CompoundTag ruleset = (CompoundTag)t;
|
||||
for (String ruleName: ruleset.getKeys())
|
||||
{
|
||||
ParsedRule<?> rule = CarpetServer.settingsManager.getRule(ruleName);
|
||||
if (rule == null)
|
||||
CarpetSettings.LOG.error("Received unknown rule: "+ruleName);
|
||||
else
|
||||
{
|
||||
CompoundTag ruleNBT = (CompoundTag) ruleset.get(ruleName);
|
||||
String value = ruleNBT.getString("Value");
|
||||
rule.set(null, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false));
|
||||
put("renderShape", (p, t) -> {
|
||||
if (CarpetClient.shapes != null)
|
||||
{
|
||||
CompoundTag boxData = (CompoundTag)t;
|
||||
CarpetClient.shapes.addShape(
|
||||
boxData.getString("type"),
|
||||
DimensionType.byRawId(boxData.getInt("dim")),
|
||||
boxData
|
||||
);
|
||||
}
|
||||
});
|
||||
}};
|
||||
|
||||
|
||||
public static void handleData(PacketByteBuf data, ClientPlayerEntity player)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
int id = data.readVarInt();
|
||||
if (id == CarpetClient.HI)
|
||||
onHi(data);
|
||||
if (id == CarpetClient.DATA)
|
||||
onSyncData(data, player);
|
||||
}
|
||||
}
|
||||
|
||||
private static void onHi(PacketByteBuf data)
|
||||
{
|
||||
synchronized (CarpetClient.sync)
|
||||
{
|
||||
CarpetClient.setCarpet();
|
||||
CarpetClient.serverCarpetVersion = data.readString(64);
|
||||
if (CarpetSettings.carpetVersion.equals(CarpetClient.serverCarpetVersion))
|
||||
{
|
||||
CarpetSettings.LOG.info("Joined carpet server with matching carpet version");
|
||||
}
|
||||
else
|
||||
{
|
||||
CarpetSettings.LOG.warn("Joined carpet server with another carpet version: "+CarpetClient.serverCarpetVersion);
|
||||
}
|
||||
if (CarpetClient.getPlayer() != null)
|
||||
respondHello();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void respondHello()
|
||||
{
|
||||
CarpetClient.getPlayer().networkHandler.sendPacket(new CustomPayloadC2SPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
(new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HELLO).writeString(CarpetSettings.carpetVersion)
|
||||
));
|
||||
}
|
||||
|
||||
private static void onSyncData(PacketByteBuf data, ClientPlayerEntity player)
|
||||
{
|
||||
CompoundTag compound = data.readCompoundTag();
|
||||
if (compound == null) return;
|
||||
for (String key: compound.getKeys())
|
||||
{
|
||||
if (dataHandlers.containsKey(key))
|
||||
dataHandlers.get(key).accept(player, compound.get(key));
|
||||
else
|
||||
CarpetSettings.LOG.error("Unknown carpet data: "+key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package carpet.network;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.settings.ParsedRule;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.PacketByteBuf;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ServerNetworkHandler
|
||||
{
|
||||
public static Set<ServerPlayerEntity> remoteCarpetPlayers = new HashSet<>();
|
||||
public static Set<ServerPlayerEntity> validCarpetPlayers = new HashSet<>();
|
||||
|
||||
public static void handleData(PacketByteBuf data, ServerPlayerEntity player)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
int id = data.readVarInt();
|
||||
if (id == CarpetClient.HELLO)
|
||||
onHello(player, data);
|
||||
}
|
||||
}
|
||||
public static void onPlayerJoin(ServerPlayerEntity playerEntity)
|
||||
{
|
||||
if (!playerEntity.networkHandler.connection.isLocal())
|
||||
{
|
||||
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
(new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HI).writeString(CarpetSettings.carpetVersion)
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
validCarpetPlayers.add(playerEntity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void onHello(ServerPlayerEntity playerEntity, PacketByteBuf packetData)
|
||||
{
|
||||
remoteCarpetPlayers.add(playerEntity);
|
||||
validCarpetPlayers.add(playerEntity);
|
||||
String clientVersion = packetData.readString(64);
|
||||
if (clientVersion.equals(CarpetSettings.carpetVersion))
|
||||
CarpetSettings.LOG.info("Player "+playerEntity.getName().getString()+" joined with a matching carpet client");
|
||||
else
|
||||
CarpetSettings.LOG.warn("Player "+playerEntity.getName().getString()+" joined with another carpet version: "+clientVersion);
|
||||
DataBuilder data = DataBuilder.create().withTickRate();
|
||||
CarpetServer.settingsManager.getRules().forEach(data::withRule);
|
||||
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() ));
|
||||
}
|
||||
|
||||
public static void updateRuleWithConnectedClients(ParsedRule<?> rule)
|
||||
{
|
||||
for (ServerPlayerEntity player : remoteCarpetPlayers)
|
||||
{
|
||||
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
DataBuilder.create().withRule(rule).build()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateTickSpeedToConnectedPlayers()
|
||||
{
|
||||
for (ServerPlayerEntity player : remoteCarpetPlayers)
|
||||
{
|
||||
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
DataBuilder.create().withTickRate().build()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendCustomCommand(String command, Tag data)
|
||||
{
|
||||
for (ServerPlayerEntity player : validCarpetPlayers)
|
||||
{
|
||||
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
DataBuilder.create().withCustomNbt(command, data).build()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendCustomCommand(ServerPlayerEntity player, String command, Tag data)
|
||||
{
|
||||
if (validCarpetPlayers.contains(player))
|
||||
{
|
||||
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
|
||||
CarpetClient.CARPET_CHANNEL,
|
||||
DataBuilder.create().withCustomNbt(command, data).build()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void onPlayerLoggedOut(ServerPlayerEntity player)
|
||||
{
|
||||
validCarpetPlayers.remove(player);
|
||||
if (!player.networkHandler.connection.isLocal())
|
||||
remoteCarpetPlayers.remove(player);
|
||||
}
|
||||
|
||||
public static void close()
|
||||
{
|
||||
remoteCarpetPlayers.clear();
|
||||
validCarpetPlayers.clear();
|
||||
}
|
||||
|
||||
private static class DataBuilder
|
||||
{
|
||||
private CompoundTag tag;
|
||||
private static DataBuilder create()
|
||||
{
|
||||
return new DataBuilder();
|
||||
}
|
||||
private DataBuilder()
|
||||
{
|
||||
tag = new CompoundTag();
|
||||
}
|
||||
private DataBuilder withTickRate()
|
||||
{
|
||||
tag.putFloat("TickRate", TickSpeed.tickrate);
|
||||
return this;
|
||||
}
|
||||
private DataBuilder withRule(ParsedRule<?> rule)
|
||||
{
|
||||
CompoundTag rules = (CompoundTag) tag.get("Rules");
|
||||
if (rules == null)
|
||||
{
|
||||
rules = new CompoundTag();
|
||||
tag.put("Rules", rules);
|
||||
}
|
||||
CompoundTag ruleNBT = new CompoundTag();
|
||||
ruleNBT.putString("Value", rule.getAsString());
|
||||
rules.put(rule.name, ruleNBT);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataBuilder withCustomNbt(String key, Tag value)
|
||||
{
|
||||
tag.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private PacketByteBuf build()
|
||||
{
|
||||
PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer());
|
||||
packetBuf.writeVarInt(CarpetClient.DATA);
|
||||
packetBuf.writeCompoundTag(tag);
|
||||
return packetBuf;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import carpet.script.exception.ContinueStatement;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.ExpressionException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.utils.ShapeDispatcher;
|
||||
import carpet.script.value.BlockValue;
|
||||
import carpet.script.value.EntityValue;
|
||||
import carpet.script.value.FormattedTextValue;
|
||||
@@ -63,7 +64,6 @@ import net.minecraft.item.TridentItem;
|
||||
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket;
|
||||
import net.minecraft.command.arguments.ItemStackArgument;
|
||||
import net.minecraft.command.arguments.ParticleArgumentType;
|
||||
import net.minecraft.enchantment.Enchantments;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
@@ -149,7 +149,6 @@ import static carpet.script.utils.WorldTools.canHasChunk;
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
import static java.lang.Math.sqrt;
|
||||
|
||||
public class CarpetExpression
|
||||
{
|
||||
@@ -262,90 +261,6 @@ public class CarpetExpression
|
||||
return bs;
|
||||
}
|
||||
|
||||
private static final Map<String, ParticleEffect> particleCache = new HashMap<>();
|
||||
private ParticleEffect getParticleData(String name)
|
||||
{
|
||||
ParticleEffect particle = particleCache.get(name);
|
||||
if (particle != null)
|
||||
return particle;
|
||||
try
|
||||
{
|
||||
particle = ParticleArgumentType.readParameters(new StringReader(name));
|
||||
}
|
||||
catch (CommandSyntaxException e)
|
||||
{
|
||||
throw new InternalExpressionException("No such particle: "+name);
|
||||
}
|
||||
particleCache.put(name, particle);
|
||||
return particle;
|
||||
}
|
||||
|
||||
|
||||
private boolean isStraight(Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
if ( (from.x == to.x && from.y == to.y) || (from.x == to.x && from.z == to.z) || (from.y == to.y && from.z == to.z))
|
||||
return from.distanceTo(to) / density > 20;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int drawOptimizedParticleLine(ServerWorld world, ParticleEffect particle, Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
double distance = from.distanceTo(to);
|
||||
int particles = (int)(distance/density);
|
||||
Vec3d towards = to.subtract(from);
|
||||
int parts = 0;
|
||||
for (PlayerEntity player : world.getPlayers())
|
||||
{
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
(towards.x)/2+from.x, (towards.y)/2+from.y, (towards.z)/2+from.z, particles/3,
|
||||
towards.x/6, towards.y/6, towards.z/6, 0.0);
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
from.x, from.y, from.z,1,0.0,0.0,0.0,0.0);
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
to.x, to.y, to.z,1,0.0,0.0,0.0,0.0);
|
||||
parts += particles/3+2;
|
||||
}
|
||||
int divider = 6;
|
||||
while (particles/divider > 1)
|
||||
{
|
||||
int center = (divider*2)/3;
|
||||
int dev = 2*divider;
|
||||
for (PlayerEntity player : world.getPlayers())
|
||||
{
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
(towards.x)/center+from.x, (towards.y)/center+from.y, (towards.z)/center+from.z, particles/divider,
|
||||
towards.x/dev, towards.y/dev, towards.z/dev, 0.0);
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
(towards.x)*(1.0-1.0/center)+from.x, (towards.y)*(1.0-1.0/center)+from.y, (towards.z)*(1.0-1.0/center)+from.z, particles/divider,
|
||||
towards.x/dev, towards.y/dev, towards.z/dev, 0.0);
|
||||
}
|
||||
parts += 2*particles/divider;
|
||||
divider = 2*divider;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private int drawParticleLine(ServerWorld world, ParticleEffect particle, Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
if (isStraight(from, to, density)) return drawOptimizedParticleLine(world, particle, from, to, density);
|
||||
double lineLengthSq = from.squaredDistanceTo(to);
|
||||
if (lineLengthSq == 0) return 0;
|
||||
Vec3d incvec = to.subtract(from).multiply(2*density/sqrt(lineLengthSq));
|
||||
int pcount = 0;
|
||||
for (Vec3d delta = new Vec3d(0.0,0.0,0.0);
|
||||
delta.lengthSquared()<lineLengthSq;
|
||||
delta = delta.add(incvec.multiply(Expression.randomizer.nextFloat())))
|
||||
{
|
||||
for (PlayerEntity player : world.getPlayers())
|
||||
{
|
||||
world.spawnParticles((ServerPlayerEntity)player, particle, true,
|
||||
delta.x+from.x, delta.y+from.y, delta.z+from.z, 1,
|
||||
0.0, 0.0, 0.0, 0.0);
|
||||
pcount ++;
|
||||
}
|
||||
}
|
||||
return pcount;
|
||||
}
|
||||
|
||||
private static void forceChunkUpdate(BlockPos pos, ServerWorld world)
|
||||
{
|
||||
@@ -2644,7 +2559,7 @@ public class CarpetExpression
|
||||
}
|
||||
}
|
||||
}
|
||||
ParticleEffect particle = getParticleData(particleName);
|
||||
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
|
||||
Vec3d vec = locator.vec;
|
||||
if (player == null)
|
||||
{
|
||||
@@ -2669,36 +2584,73 @@ public class CarpetExpression
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
ServerWorld world = cc.s.getWorld();
|
||||
String particleName = lv.get(0).evalValue(c).getString();
|
||||
ParticleEffect particle = getParticleData(particleName);
|
||||
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
|
||||
Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1);
|
||||
Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset);
|
||||
int offset = pos2.offset;
|
||||
double density = (lv.size() > offset)? NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble():1.0;
|
||||
if (density <= 0)
|
||||
double density = 1.0;
|
||||
ServerPlayerEntity player = null;
|
||||
if (lv.size() > pos2.offset+0 )
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble();
|
||||
if (density <= 0)
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
}
|
||||
if (lv.size() > pos2.offset+1)
|
||||
{
|
||||
Value playerValue = lv.get(pos2.offset+1).evalValue(c);
|
||||
if (playerValue instanceof EntityValue)
|
||||
{
|
||||
Entity e = ((EntityValue) playerValue).getEntity();
|
||||
if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_line' player argument has to be a player");
|
||||
player = (ServerPlayerEntity) e;
|
||||
}
|
||||
else
|
||||
{
|
||||
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Value retval = new NumericValue(drawParticleLine(world, particle, pos1.vec, pos2.vec, density));
|
||||
|
||||
Value retval = new NumericValue(ShapeDispatcher.drawParticleLine(
|
||||
(player == null)?world.getPlayers():Collections.singletonList(player),
|
||||
particle, pos1.vec, pos2.vec, density));
|
||||
|
||||
return (c_, t_) -> retval;
|
||||
});
|
||||
|
||||
this.expr.addLazyFunction("particle_rect", -1, (c, t, lv) ->
|
||||
this.expr.addLazyFunction("particle_box", -1, (c, t, lv) ->
|
||||
{
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
ServerWorld world = cc.s.getWorld();
|
||||
String particleName = lv.get(0).evalValue(c).getString();
|
||||
ParticleEffect particle = getParticleData(particleName);
|
||||
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
|
||||
Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1);
|
||||
Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset);
|
||||
int offset = pos2.offset;
|
||||
|
||||
double density = 1.0;
|
||||
if (lv.size() > offset)
|
||||
ServerPlayerEntity player = null;
|
||||
if (lv.size() > pos2.offset+0 )
|
||||
{
|
||||
density = NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble();
|
||||
}
|
||||
if (density <= 0)
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble();
|
||||
if (density <= 0)
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
}
|
||||
if (lv.size() > pos2.offset+1)
|
||||
{
|
||||
Value playerValue = lv.get(pos2.offset+1).evalValue(c);
|
||||
if (playerValue instanceof EntityValue)
|
||||
{
|
||||
Entity e = ((EntityValue) playerValue).getEntity();
|
||||
if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_box' player argument has to be a player");
|
||||
player = (ServerPlayerEntity) e;
|
||||
}
|
||||
else
|
||||
{
|
||||
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec3d a = pos1.vec;
|
||||
Vec3d b = pos2.vec;
|
||||
@@ -2708,24 +2660,107 @@ public class CarpetExpression
|
||||
double bx = max(a.x, b.x);
|
||||
double by = max(a.y, b.y);
|
||||
double bz = max(a.z, b.z);
|
||||
int pc = 0;
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, by, az), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(bx, by, az), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, ay, az), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(ax, ay, az), density);
|
||||
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, ay, bz), new Vec3d(ax, by, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, by, bz), new Vec3d(bx, by, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, by, bz), new Vec3d(bx, ay, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, ay, bz), new Vec3d(ax, ay, bz), density);
|
||||
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, ay, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(ax, by, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, by, bz), density);
|
||||
pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(bx, ay, bz), density);
|
||||
int particleCount = pc;
|
||||
int particleCount = ShapeDispatcher.Box.mesh(
|
||||
player==null?world.getPlayers():Collections.singletonList(player),
|
||||
particle, density, ax, ay, az, bx, by, bz
|
||||
);
|
||||
return (c_, t_) -> new NumericValue(particleCount);
|
||||
});
|
||||
// deprecated
|
||||
this.expr.alias("particle_rect", "particle_box");
|
||||
|
||||
|
||||
this.expr.addLazyFunction("marker_box", -1, (c, t, lv) ->
|
||||
{
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
Vector3Argument from = Vector3Argument.findIn(cc, lv, 0);
|
||||
Vector3Argument to = Vector3Argument.findIn(cc, lv, from.offset);
|
||||
int duration = 10;
|
||||
int color = -1; // hwite
|
||||
ServerPlayerEntity player = null;
|
||||
|
||||
if (lv.size() > to.offset+0 )
|
||||
{
|
||||
duration = NumericValue.asNumber(lv.get(to.offset).evalValue(c)).getInt();
|
||||
if (duration <= 0)
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
}
|
||||
if (lv.size() > to.offset + 1)
|
||||
{
|
||||
color = NumericValue.asNumber(lv.get(to.offset+1).evalValue(c)).getInt();
|
||||
|
||||
|
||||
if (lv.size() > to.offset + 2)
|
||||
{
|
||||
Value playerValue = lv.get(to.offset + 2).evalValue(c);
|
||||
if (playerValue instanceof EntityValue)
|
||||
{
|
||||
Entity e = ((EntityValue) playerValue).getEntity();
|
||||
if (!(e instanceof ServerPlayerEntity))
|
||||
throw new InternalExpressionException("'particle_box' player argument has to be a player");
|
||||
player = (ServerPlayerEntity) e;
|
||||
}
|
||||
else
|
||||
{
|
||||
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ShapeDispatcher.sendShape(
|
||||
(player==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player),
|
||||
cc.s.getWorld().getDimension().getType(),
|
||||
new ShapeDispatcher.Box(duration, from.vec, to.vec, color)
|
||||
);
|
||||
return LazyValue.TRUE;
|
||||
});
|
||||
|
||||
this.expr.addLazyFunction("marker_line", -1, (c, t, lv) ->
|
||||
{
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
Vector3Argument from = Vector3Argument.findIn(cc, lv, 0);
|
||||
Vector3Argument to = Vector3Argument.findIn(cc, lv, from.offset);
|
||||
int duration = 10;
|
||||
int color = -1; // hwite
|
||||
ServerPlayerEntity player = null;
|
||||
|
||||
if (lv.size() > to.offset+0 )
|
||||
{
|
||||
duration = NumericValue.asNumber(lv.get(to.offset).evalValue(c)).getInt();
|
||||
if (duration <= 0)
|
||||
{
|
||||
throw new InternalExpressionException("Particle density should be positive");
|
||||
}
|
||||
if (lv.size() > to.offset + 1)
|
||||
{
|
||||
color = NumericValue.asNumber(lv.get(to.offset+1).evalValue(c)).getInt();
|
||||
|
||||
|
||||
if (lv.size() > to.offset + 2)
|
||||
{
|
||||
Value playerValue = lv.get(to.offset + 2).evalValue(c);
|
||||
if (playerValue instanceof EntityValue)
|
||||
{
|
||||
Entity e = ((EntityValue) playerValue).getEntity();
|
||||
if (!(e instanceof ServerPlayerEntity))
|
||||
throw new InternalExpressionException("'particle_box' player argument has to be a player");
|
||||
player = (ServerPlayerEntity) e;
|
||||
}
|
||||
else
|
||||
{
|
||||
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ShapeDispatcher.sendShape(
|
||||
(player==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player),
|
||||
cc.s.getWorld().getDimension().getType(),
|
||||
new ShapeDispatcher.Line(duration, from.vec, to.vec, color)
|
||||
);
|
||||
return LazyValue.TRUE;
|
||||
});
|
||||
|
||||
this.expr.addLazyFunction("create_marker", -1, (c, t, lv) ->{
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
|
||||
@@ -418,6 +418,12 @@ public class Expression
|
||||
return result;
|
||||
}
|
||||
|
||||
public void alias(String copy, String original)
|
||||
{
|
||||
functions.put(copy, functions.get(original));
|
||||
}
|
||||
|
||||
|
||||
private void UserDefinedFunctionsAndControlFlow() // public just to get the javadoc right
|
||||
{
|
||||
// artificial construct to handle user defined functions and function definitions
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
package carpet.script.utils;
|
||||
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.script.Expression;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.command.arguments.ParticleArgumentType;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.particle.ParticleEffect;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.lang.Math.sqrt;
|
||||
|
||||
public class ShapeDispatcher
|
||||
{
|
||||
private static final Map<String, ParticleEffect> particleCache = new HashMap<>();
|
||||
|
||||
public static void sendShape(List<ServerPlayerEntity> players, DimensionType dim, ExpiringShape shape)
|
||||
{
|
||||
Consumer<ServerPlayerEntity> alternative = null;
|
||||
CompoundTag tag = null;
|
||||
for (ServerPlayerEntity player : players)
|
||||
{
|
||||
if (ServerNetworkHandler.validCarpetPlayers.contains(player))
|
||||
{
|
||||
if (tag == null) tag = shape.toTag();
|
||||
tag.putInt("dim", dim.getRawId());
|
||||
ServerNetworkHandler.sendCustomCommand(player,"renderShape", tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alternative == null) alternative = shape.alternative();
|
||||
alternative.accept(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendBox(ServerPlayerEntity player, int duration, Vec3d from, Vec3d to, int color)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static ParticleEffect getParticleData(String name)
|
||||
{
|
||||
ParticleEffect particle = particleCache.get(name);
|
||||
if (particle != null)
|
||||
return particle;
|
||||
try
|
||||
{
|
||||
particle = ParticleArgumentType.readParameters(new StringReader(name));
|
||||
}
|
||||
catch (CommandSyntaxException e)
|
||||
{
|
||||
throw new InternalExpressionException("No such particle: "+name);
|
||||
}
|
||||
particleCache.put(name, particle);
|
||||
return particle;
|
||||
}
|
||||
|
||||
|
||||
public abstract static class ExpiringShape
|
||||
{
|
||||
protected float r, g, b, a;
|
||||
protected int color;
|
||||
protected int duration = 0;
|
||||
public ExpiringShape(int duration, int color)
|
||||
{
|
||||
this.r = (float)(color >> 24 & 0xFF) / 255.0F;
|
||||
this.g = (float)(color >> 16 & 0xFF) / 255.0F;
|
||||
this.b = (float)(color >> 8 & 0xFF) / 255.0F;
|
||||
this.a = (float)(color & 0xFF) / 255.0F;
|
||||
this.color = color;
|
||||
this.duration = duration;
|
||||
};
|
||||
|
||||
public int getExpiry() { return duration; };
|
||||
public CompoundTag toTag()
|
||||
{
|
||||
CompoundTag tag = new CompoundTag();
|
||||
tag.putInt("color", color);
|
||||
tag.putInt("duration", duration);
|
||||
return tag;
|
||||
};
|
||||
public abstract Consumer<ServerPlayerEntity> alternative();
|
||||
}
|
||||
|
||||
public static class Box extends ExpiringShape
|
||||
{
|
||||
float x1, y1, z1;
|
||||
float x2, y2, z2;
|
||||
public Box(int duration, Vec3d from, Vec3d to, int color)
|
||||
{
|
||||
super(duration, color);
|
||||
x1 = (float)from.x;
|
||||
y1 = (float)from.y;
|
||||
z1 = (float)from.z;
|
||||
x2 = (float)to.x;
|
||||
y2 = (float)to.y;
|
||||
z2 = (float)to.z;
|
||||
}
|
||||
|
||||
|
||||
public static ExpiringShape fromTag(CompoundTag tag)
|
||||
{
|
||||
return new Box(tag.getInt("duration"),
|
||||
new Vec3d(tag.getFloat("x1"), tag.getFloat("y1"), tag.getFloat("z1")),
|
||||
new Vec3d(tag.getFloat("x2"), tag.getFloat("y2"), tag.getFloat("z2")),
|
||||
tag.getInt("color")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag toTag()
|
||||
{
|
||||
CompoundTag tag = super.toTag();
|
||||
tag.putFloat("x1", x1);
|
||||
tag.putFloat("y1", y1);
|
||||
tag.putFloat("z1", z1);
|
||||
tag.putFloat("x2", x2);
|
||||
tag.putFloat("y2", y2);
|
||||
tag.putFloat("z2", z2);
|
||||
tag.putString("type", "debugBox");
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<ServerPlayerEntity> alternative()
|
||||
{
|
||||
String particleName = String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", r, g, b);
|
||||
ParticleEffect particle = getParticleData(particleName);
|
||||
double dx = x1-x2;
|
||||
double dy = y1-y2;
|
||||
double dz = z1-z2;
|
||||
double density = Math.max(2.0, Math.sqrt(dx*dx+dy*dy+dz*dz) /50) / (a+0.1);
|
||||
return p -> mesh(Collections.singletonList(p), particle, density, x1, y1, z1, x2, y2, z2);
|
||||
}
|
||||
|
||||
public static int mesh(List<ServerPlayerEntity> playerList, ParticleEffect particle, double density,
|
||||
double x1, double y1, double z1, double x2, double y2, double z2 )
|
||||
{
|
||||
return
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y2, z1), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x2, y2, z1), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y1, z1), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x1, y1, z1), density)+
|
||||
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y1, z2), new Vec3d(x1, y2, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y2, z2), new Vec3d(x2, y2, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y2, z2), new Vec3d(x2, y1, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y1, z2), new Vec3d(x1, y1, z2), density)+
|
||||
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y1, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x1, y2, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y2, z2), density)+
|
||||
drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x2, y1, z2), density);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Line extends ExpiringShape
|
||||
{
|
||||
float x1, y1, z1;
|
||||
float x2, y2, z2;
|
||||
public Line(int duration, Vec3d from, Vec3d to, int color)
|
||||
{
|
||||
super(duration, color);
|
||||
x1 = (float)from.x;
|
||||
y1 = (float)from.y;
|
||||
z1 = (float)from.z;
|
||||
x2 = (float)to.x;
|
||||
y2 = (float)to.y;
|
||||
z2 = (float)to.z;
|
||||
|
||||
}
|
||||
@Override
|
||||
public CompoundTag toTag()
|
||||
{
|
||||
CompoundTag tag = super.toTag();
|
||||
tag.putFloat("x1", x1);
|
||||
tag.putFloat("y1", y1);
|
||||
tag.putFloat("z1", z1);
|
||||
tag.putFloat("x2", x2);
|
||||
tag.putFloat("y2", y2);
|
||||
tag.putFloat("z2", z2);
|
||||
tag.putString("type", "debugLine");
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static ExpiringShape fromTag(CompoundTag tag)
|
||||
{
|
||||
return new Line(tag.getInt("duration"),
|
||||
new Vec3d(tag.getFloat("x1"), tag.getFloat("y1"), tag.getFloat("z1")),
|
||||
new Vec3d(tag.getFloat("x2"), tag.getFloat("y2"), tag.getFloat("z2")),
|
||||
tag.getInt("color")
|
||||
);
|
||||
}
|
||||
@Override
|
||||
public Consumer<ServerPlayerEntity> alternative()
|
||||
{
|
||||
String particleName = String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", r, g, b);
|
||||
ParticleEffect particle = getParticleData(particleName);
|
||||
Vec3d pos1 = new Vec3d(x1, y1, z1);
|
||||
Vec3d pos2 = new Vec3d(x2, y2, z2);
|
||||
double dx = x1-x2;
|
||||
double dy = y1-y2;
|
||||
double dz = z1-z2;
|
||||
double density = Math.max(2.0, Math.sqrt(dx*dx+dy*dy+dz*dz) /50) / (a+0.1);
|
||||
return p -> drawParticleLine(Collections.singletonList(p), particle, pos1, pos2, density);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isStraight(Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
if ( (from.x == to.x && from.y == to.y) || (from.x == to.x && from.z == to.z) || (from.y == to.y && from.z == to.z))
|
||||
return from.distanceTo(to) / density > 20;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int drawOptimizedParticleLine(List<ServerPlayerEntity> playerList, ParticleEffect particle, Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
double distance = from.distanceTo(to);
|
||||
int particles = (int)(distance/density);
|
||||
Vec3d towards = to.subtract(from);
|
||||
int parts = 0;
|
||||
for (ServerPlayerEntity player : playerList)
|
||||
{
|
||||
ServerWorld world = player.getServerWorld();
|
||||
world.spawnParticles(player, particle, true,
|
||||
(towards.x)/2+from.x, (towards.y)/2+from.y, (towards.z)/2+from.z, particles/3,
|
||||
towards.x/6, towards.y/6, towards.z/6, 0.0);
|
||||
world.spawnParticles(player, particle, true,
|
||||
from.x, from.y, from.z,1,0.0,0.0,0.0,0.0);
|
||||
world.spawnParticles(player, particle, true,
|
||||
to.x, to.y, to.z,1,0.0,0.0,0.0,0.0);
|
||||
parts += particles/3+2;
|
||||
}
|
||||
int divider = 6;
|
||||
while (particles/divider > 1)
|
||||
{
|
||||
int center = (divider*2)/3;
|
||||
int dev = 2*divider;
|
||||
for (ServerPlayerEntity player : playerList)
|
||||
{
|
||||
ServerWorld world = player.getServerWorld();
|
||||
world.spawnParticles(player, particle, true,
|
||||
(towards.x)/center+from.x, (towards.y)/center+from.y, (towards.z)/center+from.z, particles/divider,
|
||||
towards.x/dev, towards.y/dev, towards.z/dev, 0.0);
|
||||
world.spawnParticles(player, particle, true,
|
||||
(towards.x)*(1.0-1.0/center)+from.x, (towards.y)*(1.0-1.0/center)+from.y, (towards.z)*(1.0-1.0/center)+from.z, particles/divider,
|
||||
towards.x/dev, towards.y/dev, towards.z/dev, 0.0);
|
||||
}
|
||||
parts += 2*particles/divider;
|
||||
divider = 2*divider;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static int drawParticleLine(List<ServerPlayerEntity> players, ParticleEffect particle, Vec3d from, Vec3d to, double density)
|
||||
{
|
||||
if (isStraight(from, to, density)) return drawOptimizedParticleLine(players, particle, from, to, density);
|
||||
double lineLengthSq = from.squaredDistanceTo(to);
|
||||
if (lineLengthSq == 0) return 0;
|
||||
Vec3d incvec = to.subtract(from).multiply(2*density/sqrt(lineLengthSq));
|
||||
int pcount = 0;
|
||||
for (Vec3d delta = new Vec3d(0.0,0.0,0.0);
|
||||
delta.lengthSquared()<lineLengthSq;
|
||||
delta = delta.add(incvec.multiply(Expression.randomizer.nextFloat())))
|
||||
{
|
||||
for (ServerPlayerEntity player : players)
|
||||
{
|
||||
player.getServerWorld().spawnParticles(player, particle, true,
|
||||
delta.x+from.x, delta.y+from.y, delta.z+from.z, 1,
|
||||
0.0, 0.0, 0.0, 0.0);
|
||||
pcount ++;
|
||||
}
|
||||
}
|
||||
return pcount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package carpet.script.utils;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.VertexConsumer;
|
||||
import net.minecraft.client.render.VertexConsumerProvider;
|
||||
import net.minecraft.client.util.math.Matrix4f;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class ShapesRenderer
|
||||
{
|
||||
private final Map<DimensionType, List<RenderedShape<? extends ShapeDispatcher.ExpiringShape>>> shapes;
|
||||
private MinecraftClient client;
|
||||
|
||||
private Map<String, BiFunction<MinecraftClient, CompoundTag, RenderedShape<? extends ShapeDispatcher.ExpiringShape >>> renderedShapes
|
||||
= new HashMap<String, BiFunction<MinecraftClient, CompoundTag, RenderedShape<? extends ShapeDispatcher.ExpiringShape>>>()
|
||||
{{
|
||||
put("debugBox", RenderedBox::new);
|
||||
put("debugLine", RenderedLine::new);
|
||||
}};
|
||||
|
||||
public ShapesRenderer(MinecraftClient minecraftClient)
|
||||
{
|
||||
this.client = minecraftClient;
|
||||
shapes = new HashMap<>();
|
||||
shapes.put(DimensionType.OVERWORLD, new ArrayList<>());
|
||||
shapes.put(DimensionType.THE_NETHER, new ArrayList<>());
|
||||
shapes.put(DimensionType.THE_END, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, double cameraX, double cameraY, double cameraZ)
|
||||
{
|
||||
//Camera camera = this.client.gameRenderer.getCamera();
|
||||
IWorld iWorld = this.client.world;
|
||||
DimensionType dimensionType = iWorld.getDimension().getType();
|
||||
if (shapes.get(dimensionType).isEmpty()) return;
|
||||
//BlockPos blockPos = new BlockPos(camera.getPos().x, 0.0D, camera.getPos().z);
|
||||
VertexConsumer vertexConsumer = vertexConsumers.getBuffer(RenderLayer.getLines());
|
||||
long currentTime = client.world.getTime();
|
||||
|
||||
synchronized (shapes)
|
||||
{
|
||||
Iterator<RenderedShape<? extends ShapeDispatcher.ExpiringShape>> it = shapes.get(dimensionType).iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
RenderedShape shape = it.next();
|
||||
if (shape.isExpired(currentTime)) it.remove();
|
||||
shape.render(matrices, vertexConsumer, (float) cameraX, (float) cameraY, (float) cameraZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void addShape(String type, DimensionType dim, CompoundTag tag)
|
||||
{
|
||||
BiFunction<MinecraftClient, CompoundTag, RenderedShape<? extends ShapeDispatcher.ExpiringShape >> shapeFactory;
|
||||
shapeFactory = renderedShapes.get(type);
|
||||
if (shapeFactory == null)
|
||||
{
|
||||
CarpetSettings.LOG.error("Unrecognized shape: "+type);
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronized (shapes) { shapes.get(dim).add(shapeFactory.apply(client, tag)); }
|
||||
}
|
||||
}
|
||||
public void reset()
|
||||
{
|
||||
synchronized (shapes)
|
||||
{
|
||||
shapes.values().forEach(List::clear);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public abstract static class RenderedShape<T extends ShapeDispatcher.ExpiringShape>
|
||||
{
|
||||
T shape;
|
||||
long expiryTick;
|
||||
public abstract void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz );
|
||||
protected RenderedShape(MinecraftClient client, T shape)
|
||||
{
|
||||
this.shape = shape;
|
||||
expiryTick = client.world.getTime()+shape.getExpiry();
|
||||
}
|
||||
|
||||
public boolean isExpired(long currentTick)
|
||||
{
|
||||
return expiryTick < currentTick;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RenderedBox extends RenderedShape<ShapeDispatcher.Box>
|
||||
{
|
||||
public RenderedBox(MinecraftClient client, CompoundTag boxData)
|
||||
{
|
||||
super(client, (ShapeDispatcher.Box)ShapeDispatcher.Box.fromTag(boxData));
|
||||
}
|
||||
@Override
|
||||
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz)
|
||||
{
|
||||
drawBox(matrices, vertexConsumer,
|
||||
shape.x1-cx, shape.y1-cy, shape.z1-cz,
|
||||
shape.x2-cx, shape.y2-cy, shape.z2-cz,
|
||||
shape.r, shape.g, shape.b, shape.a, shape.r, shape.g, shape.b
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RenderedLine extends RenderedShape<ShapeDispatcher.Line>
|
||||
{
|
||||
public RenderedLine(MinecraftClient client, CompoundTag boxData)
|
||||
{
|
||||
super(client, (ShapeDispatcher.Line)ShapeDispatcher.Line.fromTag(boxData));
|
||||
}
|
||||
@Override
|
||||
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz)
|
||||
{
|
||||
drawLine(matrices, vertexConsumer,
|
||||
shape.x1-cx, shape.y1-cy, shape.z1-cz,
|
||||
shape.x2-cx, shape.y2-cy, shape.z2-cz,
|
||||
shape.r, shape.g, shape.b, shape.a
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// some raw shit
|
||||
|
||||
public static void drawLine(MatrixStack matrix, VertexConsumer vertexConsumer, float x1, float y1, float z1, float x2, float y2, float z2, float red1, float grn1, float blu1, float alpha) {
|
||||
Matrix4f matrix4f = matrix.peek().getModel();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
}
|
||||
|
||||
public static void drawBox(MatrixStack matrix, VertexConsumer vertexConsumer, float x1, float y1, float z1, float x2, float y2, float z2, float red1, float grn1, float blu1, float alpha, float red2, float grn2, float blu2) {
|
||||
Matrix4f matrix4f = matrix.peek().getModel();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red1, grn2, blu2, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn2, blu2, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red2, grn1, blu2, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red2, grn1, blu2, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red2, grn2, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red2, grn2, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next();
|
||||
vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next();
|
||||
}
|
||||
}
|
||||
@@ -253,6 +253,7 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
while (Translations.hasTranslation(keyBase+i))
|
||||
{
|
||||
extras.add(Translations.tr(keyBase+i));
|
||||
i++;
|
||||
}
|
||||
return (extras.isEmpty()) ? extraInfo : extras;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package carpet.settings;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.utils.Translations;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -81,7 +82,7 @@ public class SettingsManager
|
||||
|
||||
public void detachServer()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(server.getCommandSource());
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(null);
|
||||
server = null;
|
||||
}
|
||||
|
||||
@@ -104,6 +105,7 @@ public class SettingsManager
|
||||
void notifyRuleChanged(ServerCommandSource source, ParsedRule<?> rule, String userTypedValue)
|
||||
{
|
||||
observers.forEach(observer -> observer.accept(source, rule, userTypedValue));
|
||||
ServerNetworkHandler.updateRuleWithConnectedClients(rule);
|
||||
}
|
||||
|
||||
public Iterable<String> getCategories()
|
||||
@@ -585,8 +587,6 @@ public class SettingsManager
|
||||
rules.get(setting).set(source, strOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public abstract class Validator<T>
|
||||
@Override
|
||||
public T validate(ServerCommandSource source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (CarpetServer.settingsManager != null)
|
||||
if (CarpetServer.settingsManager != null && source != null)
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@@ -253,7 +253,8 @@ public class Messenger
|
||||
//message source
|
||||
public static void m(ServerCommandSource source, Object ... fields)
|
||||
{
|
||||
source.sendFeedback(Messenger.c(fields),source.getMinecraftServer() != null && source.getMinecraftServer().getWorld(World.field_25179) != null); //OW
|
||||
if (source != null)
|
||||
source.sendFeedback(Messenger.c(fields),source.getMinecraftServer() != null && source.getMinecraftServer().getWorld(World.field_25179) != null); //OW
|
||||
}
|
||||
public static void m(PlayerEntity player, Object ... fields)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user