base mod created

This commit is contained in:
Mohammad-Ali Minaie
2018-10-08 09:07:47 -04:00
parent 0a7700c356
commit b86dedad2f
7848 changed files with 584664 additions and 1 deletions

View File

@@ -0,0 +1,168 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server;
import java.lang.ref.WeakReference;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapMaker;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.server.timings.TimeTracker;
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see net.minecraftforge.server.timings.TimeTracker
*/
@Deprecated
public class ForgeTimeTracker {
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see net.minecraftforge.server.timings.TimeTracker
*/
@Deprecated
public static boolean tileEntityTracking;
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see net.minecraftforge.server.timings.TimeTracker
*/
@Deprecated
public static int tileEntityTrackingDuration;
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see net.minecraftforge.server.timings.TimeTracker
*/
@Deprecated
public static long tileEntityTrackingTime;
private Map<TileEntity,int[]> tileEntityTimings;
private WeakReference<TileEntity> tile;
private static final ForgeTimeTracker INSTANCE = new ForgeTimeTracker();
/* not implemented
private WeakReference<Entity> entity;
private Map<Entity,int[]> entityTimings;
*/
private long timing;
private ForgeTimeTracker()
{
MapMaker mm = new MapMaker();
mm.weakKeys();
tileEntityTimings = mm.makeMap();
//entityTimings = mm.makeMap();
}
private void trackTileStart(TileEntity tileEntity, long nanoTime)
{
if (tileEntityTrackingTime == 0)
{
tileEntityTrackingTime = nanoTime;
}
else if (tileEntityTrackingTime + tileEntityTrackingDuration < nanoTime)
{
tileEntityTracking = false;
tileEntityTrackingTime = 0;
return;
}
tile = new WeakReference<TileEntity>(tileEntity);
timing = nanoTime;
}
private void trackTileEnd(TileEntity tileEntity, long nanoTime)
{
if (tile == null || tile.get() != tileEntity)
{
tile = null;
// race, exit
return;
}
int[] timings = tileEntityTimings.computeIfAbsent(tileEntity, k -> new int[101]);
int idx = timings[100] = (timings[100] + 1) % 100;
timings[idx] = (int) (nanoTime - timing);
}
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see TimeTracker#getTimingData()
*/
@Deprecated
public static ImmutableMap<TileEntity,int[]> getTileTimings()
{
return INSTANCE.buildImmutableTileEntityTimingMap();
}
private ImmutableMap<TileEntity, int[]> buildImmutableTileEntityTimingMap()
{
ImmutableMap.Builder<TileEntity, int[]> builder = new ImmutableMap.Builder<>();
TimeTracker.TILE_ENTITY_UPDATE.getTimingData().stream()
.filter(t -> t.getObject().get() != null).forEach(e -> builder.put(e.getObject().get(), e.getRawTimingData()));
return builder.build();
}
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see TimeTracker#trackStart(Object)
*/
@Deprecated
public static void trackStart(TileEntity tileEntity)
{
TimeTracker.TILE_ENTITY_UPDATE.trackStart(tileEntity);
}
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see TimeTracker#trackEnd(Object)
*/
@Deprecated
public static void trackEnd(TileEntity tileEntity)
{
TimeTracker.TILE_ENTITY_UPDATE.trackEnd(tileEntity);
}
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see TimeTracker#trackStart(Object)
*/
@Deprecated
public static void trackStart(Entity par1Entity)
{
}
/**
* @deprecated To be removed in 1.13 - Implementation has been moved
* @see TimeTracker#trackEnd(Object)
*/
@Deprecated
public static void trackEnd(Entity par1Entity)
{
}
}

View File

@@ -0,0 +1,197 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.management.PlayerChunkMapEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentBase;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.MinecraftException;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
public class ChunkGenWorker implements IWorker
{
private final ICommandSender listener;
protected final BlockPos start;
protected final int total;
private final int dim;
private final Queue<BlockPos> queue;
private final int notificationFrequency;
private int lastNotification = 0;
private long lastNotifcationTime = 0;
private int genned = 0;
private Boolean keepingLoaded;
public ChunkGenWorker(ICommandSender listener, BlockPos start, int total, int dim, int interval)
{
this.listener = listener;
this.start = start;
this.total = total;
this.dim = dim;
this.queue = buildQueue();
this.notificationFrequency = interval != -1 ? interval : Math.max(total / 20, 100); //Every 5% or every 100, whichever is more.
this.lastNotifcationTime = System.currentTimeMillis(); //We also notify at least once every 60 seconds, to show we haven't froze.
}
protected Queue<BlockPos> buildQueue()
{
Queue<BlockPos> ret = new ArrayDeque<BlockPos>();
ret.add(start);
//This *should* spiral outwards, starting on right side, down, left, up, right, but hey we'll see!
int radius = 1;
while (ret.size() < total)
{
for (int q = -radius + 1; q <= radius && ret.size() < total; q++)
ret.add(start.add(radius, 0, q));
for (int q = radius - 1; q >= -radius && ret.size() < total; q--)
ret.add(start.add(q, 0, radius));
for (int q = radius - 1; q >= -radius && ret.size() < total; q--)
ret.add(start.add(-radius, 0, q));
for (int q = -radius + 1; q <= radius && ret.size() < total; q++)
ret.add(start.add(q, 0, -radius));
radius++;
}
return ret;
}
@Deprecated // TODO remove in 1.13
public TextComponentTranslation getStartMessage()
{
return new TextComponentTranslation("commands.forge.gen.start", total, start.getX(), start.getZ(), dim);
}
public TextComponentBase getStartMessage(ICommandSender sender)
{
return TextComponentHelper.createComponentTranslation(sender, "commands.forge.gen.start", total, start.getX(), start.getZ(), dim);
}
@Override
public boolean hasWork()
{
return queue.size() > 0;
}
@Override
public boolean doWork()
{
WorldServer world = DimensionManager.getWorld(dim);
if (world == null)
{
DimensionManager.initDimension(dim);
world = DimensionManager.getWorld(dim);
if (world == null)
{
listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.dim_fail", dim));
queue.clear();
return false;
}
}
AnvilChunkLoader loader = world.getChunkProvider().chunkLoader instanceof AnvilChunkLoader ? (AnvilChunkLoader)world.getChunkProvider().chunkLoader : null;
if (loader != null && loader.getPendingSaveCount() > 100)
{
if (lastNotifcationTime < System.currentTimeMillis() - 10*1000)
{
listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.progress", total - queue.size(), total));
lastNotifcationTime = System.currentTimeMillis();
}
return false;
}
BlockPos next = queue.poll();
if (next != null)
{
// While we work we don't want to cause world load spam so pause unloading the world.
if (keepingLoaded == null)
{
keepingLoaded = DimensionManager.keepDimensionLoaded(dim, true);
}
if (++lastNotification >= notificationFrequency || lastNotifcationTime < System.currentTimeMillis() - 60*1000)
{
listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.progress", total - queue.size(), total));
lastNotification = 0;
lastNotifcationTime = System.currentTimeMillis();
}
int x = next.getX();
int z = next.getZ();
Chunk target = world.getChunkFromChunkCoords(x, z);
Chunk[] chunks = { target };
if (!target.isTerrainPopulated())
{
// In order for a chunk to populate, The chunks around its bottom right corner need to be loaded.
// So lets load those chunks, but this needs to be done in a certain order to make this trigger.
// So this does load more chunks then it should, and is a hack, but lets go!.
chunks = new Chunk[] {
target,
world.getChunkFromChunkCoords(x + 1, z),
world.getChunkFromChunkCoords(x + 1, z + 1),
world.getChunkFromChunkCoords(x, z + 1),
};
try
{
world.getChunkProvider().chunkLoader.saveChunk(world, target);
}
catch (IOException | MinecraftException e)
{
listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.saveerror", e.getMessage()));
}
genned++;
}
for (Chunk chunk : chunks) //Now lets unload them. Note: Saving is done off thread so there may be cache hits, but this should still unload everything.
{
PlayerChunkMapEntry watchers = world.getPlayerChunkMap().getEntry(chunk.x, chunk.z);
if (watchers == null) //If there are no players watching this, this will be null, so we can unload.
world.getChunkProvider().queueUnload(chunk);
}
}
if (queue.size() == 0)
{
listener.sendMessage(TextComponentHelper.createComponentTranslation(listener, "commands.forge.gen.complete", genned, total, dim));
if (keepingLoaded != null && keepingLoaded)
{
DimensionManager.keepDimensionLoaded(dim, false);
}
return false;
}
return true;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.DimensionType;
import net.minecraftforge.common.DimensionManager;
import java.util.Map;
public class CommandDimensions extends CommandBase
{
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "dimensions";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.dimensions.usage";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 0;
}
/**
* Check if the given ICommandSender has permission to execute this command
*/
@Override
public boolean checkPermission(MinecraftServer server, ICommandSender sender)
{
return true;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.dimensions.list"));
for (Map.Entry<DimensionType, IntSortedSet> entry : DimensionManager.getRegisteredDimensions().entrySet())
{
sender.sendMessage(new TextComponentString(entry.getKey().getName() + ": " + entry.getValue()));
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.Maps;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.DimensionManager;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
class CommandEntity extends CommandTreeBase
{
public CommandEntity()
{
addSubcommand(new EntityListCommand());
addSubcommand(new CommandTreeHelp(this));
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.entity.usage";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "entity";
}
private static class EntityListCommand extends CommandBase
{
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "list";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.entity.list.usage";
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
String filter = "*";
if (args.length > 0)
{
filter = args[0];
}
final String cleanFilter = filter.replace("?", ".?").replace("*", ".*?");
Set<ResourceLocation> names = EntityList.getEntityNameList().stream().filter(n -> n.toString().matches(cleanFilter)).collect(Collectors.toSet());
if (names.isEmpty())
throw new WrongUsageException("commands.forge.entity.list.invalid");
int dim = args.length > 1 ? parseInt(args[1]) : sender.getEntityWorld().provider.getDimension();
WorldServer world = DimensionManager.getWorld(dim);
if (world == null)
throw new WrongUsageException("commands.forge.entity.list.invalidworld", dim);
Map<ResourceLocation, MutablePair<Integer, Map<ChunkPos, Integer>>> list = Maps.newHashMap();
List<Entity> entities = world.loadedEntityList;
entities.forEach(e -> {
ResourceLocation key = EntityList.getKey(e);
MutablePair<Integer, Map<ChunkPos, Integer>> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap()));
ChunkPos chunk = new ChunkPos(e.getPosition());
info.left++;
info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1);
});
if (names.size() == 1)
{
ResourceLocation name = names.iterator().next();
Pair<Integer, Map<ChunkPos, Integer>> info = list.get(name);
if (info == null)
throw new WrongUsageException("commands.forge.entity.list.none");
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.entity.list.single.header", name, info.getLeft()));
List<Map.Entry<ChunkPos, Integer>> toSort = new ArrayList<>();
toSort.addAll(info.getRight().entrySet());
toSort.sort((a, b) -> {
if (Objects.equals(a.getValue(), b.getValue()))
{
return a.getKey().toString().compareTo(b.getKey().toString());
}
else
{
return b.getValue() - a.getValue();
}
});
long limit = 10;
for (Map.Entry<ChunkPos, Integer> e : toSort)
{
if (limit-- == 0) break;
sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z));
}
}
else
{
List<Pair<ResourceLocation, Integer>> info = new ArrayList<>();
list.forEach((key, value) -> {
if (names.contains(key))
{
Pair<ResourceLocation, Integer> of = Pair.of(key, value.left);
info.add(of);
}
});
info.sort((a, b) -> {
if (Objects.equals(a.getRight(), b.getRight()))
{
return a.getKey().toString().compareTo(b.getKey().toString());
}
else
{
return b.getRight() - a.getRight();
}
});
if (info.size() == 0)
throw new WrongUsageException("commands.forge.entity.list.none");
int count = info.stream().mapToInt(Pair::getRight).sum();
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.entity.list.multiple.header", count));
info.forEach(e -> sender.sendMessage(new TextComponentString(" " + e.getValue() + ": " + e.getKey())));
}
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
if (args.length == 1)
{
String[] entityNames = EntityList.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new);
return getListOfStringsMatchingLastWord(args, entityNames);
}
return Collections.emptyList();
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.WorldWorkerManager;
class CommandGenerate extends CommandBase
{
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "generate";
}
/**
* Get a list of aliases for this command. <b>Never return null!</b>
*/
@Override
public List<String> getAliases()
{
return Collections.singletonList("gen");
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.gen.usage";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 4;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
// x y z chunkCount [dim] [interval]
if (args.length < 4)
{
throw new WrongUsageException("commands.forge.gen.usage");
}
BlockPos blockpos = parseBlockPos(sender, args, 0, false);
int count = parseInt(args[3], 10);
int dim = args.length >= 5 ? parseInt(args[4]) : sender.getEntityWorld().provider.getDimension();
int interval = args.length >= 6 ? parseInt(args[5]) : -1;
BlockPos chunkpos = new BlockPos(blockpos.getX() >> 4, 0, blockpos.getZ() >> 4);
ChunkGenWorker worker = new ChunkGenWorker(sender, chunkpos, count, dim, interval);
sender.sendMessage(worker.getStartMessage(sender));
WorldWorkerManager.addWorker(worker);
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
if (args.length < 4)
{
return getTabCompletionCoordinate(args, 0, targetPos);
}
// Chunk Count? No completion
// Dimension, Add support for names? Get list of ids? Meh
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,138 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.Entity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.util.ITeleporter;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
public class CommandSetDimension extends CommandBase
{
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "setdimension";
}
/**
* Get a list of aliases for this command. <b>Never return null!</b>
*/
@Override
public List<String> getAliases()
{
return Collections.singletonList("setdim");
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.setdim.usage";
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
if (args.length > 2 && args.length <= 5)
{
return getTabCompletionCoordinate(args, 2, targetPos);
}
return Collections.emptyList();
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
// args: <entity> <dim> [<x> <y> <z>]
if (args.length != 2 && args.length != 5)
{
throw new WrongUsageException("commands.forge.setdim.usage");
}
Entity entity = getEntity(server, sender, args[0]);
if (!checkEntity(entity))
{
throw new CommandException("commands.forge.setdim.invalid.entity", entity.getName());
}
int dimension = parseInt(args[1]);
if (!DimensionManager.isDimensionRegistered(dimension))
{
throw new CommandException("commands.forge.setdim.invalid.dim", dimension);
}
if (dimension == entity.dimension)
{
throw new CommandException("commands.forge.setdim.invalid.nochange", entity.getName(), dimension);
}
BlockPos pos = args.length == 5 ? parseBlockPos(sender, args, 2, false) : sender.getPosition();
entity.changeDimension(dimension, new CommandTeleporter(pos));
}
private static boolean checkEntity(Entity entity)
{
// use vanilla portal logic, try to avoid doing anything too silly
return !entity.isRiding() && !entity.isBeingRidden() && entity.isNonBoss();
}
private static class CommandTeleporter implements ITeleporter
{
private final BlockPos targetPos;
private CommandTeleporter(BlockPos targetPos)
{
this.targetPos = targetPos;
}
@Override
public void placeEntity(World world, Entity entity, float yaw)
{
entity.moveToBlockPosAndAngles(targetPos, yaw, entity.rotationPitch);
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import java.text.DecimalFormat;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.DimensionType;
import net.minecraftforge.common.DimensionManager;
class CommandTps extends CommandBase
{
private static final DecimalFormat TIME_FORMATTER = new DecimalFormat("########0.000");
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "tps";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tps.usage";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 0;
}
/**
* Check if the given ICommandSender has permission to execute this command
*/
@Override
public boolean checkPermission(MinecraftServer server, ICommandSender sender)
{
return true;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
int dim = 0;
boolean summary = true;
if (args.length > 0)
{
dim = parseInt(args[0]);
summary = false;
}
if (summary)
{
for (Integer dimId : DimensionManager.getIDs())
{
double worldTickTime = mean(server.worldTickTimes.get(dimId)) * 1.0E-6D;
double worldTPS = Math.min(1000.0/worldTickTime, 20);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary", getDimensionPrefix(dimId), TIME_FORMATTER.format(worldTickTime), TIME_FORMATTER.format(worldTPS)));
}
double meanTickTime = mean(server.tickTimeArray) * 1.0E-6D;
double meanTPS = Math.min(1000.0/meanTickTime, 20);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary","Overall", TIME_FORMATTER.format(meanTickTime), TIME_FORMATTER.format(meanTPS)));
}
else
{
double worldTickTime = mean(server.worldTickTimes.get(dim)) * 1.0E-6D;
double worldTPS = Math.min(1000.0/worldTickTime, 20);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tps.summary", getDimensionPrefix(dim), TIME_FORMATTER.format(worldTickTime), TIME_FORMATTER.format(worldTPS)));
}
}
private static String getDimensionPrefix(int dimId)
{
DimensionType providerType = DimensionManager.getProviderType(dimId);
if (providerType == null)
{
return String.format("Dim %2d", dimId);
}
else
{
return String.format("Dim %2d (%s)", dimId, providerType.getName());
}
}
private static long mean(long[] values)
{
long sum = 0L;
for (long v : values)
{
sum += v;
}
return sum / values.length;
}
}

View File

@@ -0,0 +1,413 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import javax.annotation.Nullable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.Entity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.DimensionType;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.server.timings.ForgeTimings;
import net.minecraftforge.server.timings.TimeTracker;
class CommandTrack extends CommandTreeBase
{
private static final DecimalFormat TIME_FORMAT = new DecimalFormat("#####0.00");
public CommandTrack()
{
addSubcommand(new StartTrackingCommand());
addSubcommand(new ResetTrackingCommand());
addSubcommand(new TrackResultsTileEntity());
addSubcommand(new TrackResultsEntity());
addSubcommand(new CommandTreeHelp(this));
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "track";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tracking.usage";
}
private static class StartTrackingCommand extends CommandBase
{
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
if (args.length != 2)
{
throw new WrongUsageException(getUsage(sender));
}
String type = args[0];
int duration = parseInt(args[1], 1, 60);
if ("te".equals(type))
{
TimeTracker.TILE_ENTITY_UPDATE.reset();
TimeTracker.TILE_ENTITY_UPDATE.enable(duration);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.te.enabled", duration));
}
else if ("entity".equals(type))
{
TimeTracker.ENTITY_UPDATE.reset();
TimeTracker.ENTITY_UPDATE.enable(duration);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.entity.enabled", duration));
}
else
{
throw new WrongUsageException(getUsage(sender));
}
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
return Arrays.asList("te", "entity");
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "start";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tracking.start.usage";
}
}
private static class ResetTrackingCommand extends CommandBase
{
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "reset";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tracking.reset.usage";
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 2;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
if (args.length != 1)
{
throw new WrongUsageException(getUsage(sender));
}
String type = args[0];
if ("te".equals(type))
{
TimeTracker.TILE_ENTITY_UPDATE.reset();
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.reset"));
}
else if ("entity".equals(type))
{
TimeTracker.ENTITY_UPDATE.reset();
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.reset"));
}
else
{
throw new WrongUsageException(getUsage(sender));
}
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
return Arrays.asList("te", "entity");
}
}
/**
* A base command for all the tracking results commands
*
* @param <T>
*/
private static abstract class TrackResultsBaseCommand<T> extends CommandBase
{
private TimeTracker<T> tracker;
protected TrackResultsBaseCommand(TimeTracker<T> tracker)
{
this.tracker = tracker;
}
/**
* Returns the time objects recorded by the time tracker sorted by average time
*
* @return A list of time objects
*/
protected List<ForgeTimings<T>> getSortedTimings()
{
ArrayList<ForgeTimings<T>> list = new ArrayList<>();
list.addAll(tracker.getTimingData());
list.sort(Comparator.comparingDouble(ForgeTimings::getAverageTimings));
Collections.reverse(list);
return list;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
List<ForgeTimings<T>> timingsList = getSortedTimings();
if (timingsList.isEmpty())
{
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.noData"));
}
else
{
timingsList.stream()
.filter(timings -> timings.getObject().get() != null)
.limit(10)
.forEach(timings -> sender.sendMessage(buildTrackString(sender, timings))
);
}
}
protected abstract ITextComponent buildTrackString(ICommandSender sender, ForgeTimings<T> data);
/**
* Gets the time suffix for the provided time in nanoseconds
*
* @param time The time in nanoseconds
* @return The time suffix
*/
protected String getTimeSuffix(double time)
{
if (time < 1000)
{
return "µs";
}
else
{
return "ms";
}
}
/**
* Translates a world dimension ID into a name
*
* @param dimId The dimension ID
* @return The name of the dimension
*/
protected String getWorldName(int dimId)
{
DimensionType type = DimensionManager.getProviderType(dimId);
if (type == null)
{
return "Dim " + dimId;
}
else
{
return type.getName();
}
}
}
private static class TrackResultsEntity extends TrackResultsBaseCommand<Entity>
{
public TrackResultsEntity()
{
super(TimeTracker.ENTITY_UPDATE);
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "entity";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tracking.entity.usage";
}
@Override
protected ITextComponent buildTrackString(ICommandSender sender, ForgeTimings<Entity> data)
{
Entity entity = data.getObject().get();
if (entity == null)
return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.invalid");
BlockPos currentPos = entity.getPosition();
String world = getWorldName(entity.world.provider.getDimension());
double averageTimings = data.getAverageTimings();
String tickTime = (averageTimings > 1000 ? TIME_FORMAT.format(averageTimings / 1000) : TIME_FORMAT.format(averageTimings)) + getTimeSuffix(
averageTimings);
return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.timingEntry", entity.getName(),
world, currentPos.getX(), currentPos.getY(), currentPos.getZ(), tickTime);
}
}
private static class TrackResultsTileEntity extends TrackResultsBaseCommand<TileEntity>
{
public TrackResultsTileEntity()
{
super(TimeTracker.TILE_ENTITY_UPDATE);
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "te";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.tracking.te.usage";
}
@Override
protected ITextComponent buildTrackString(ICommandSender sender, ForgeTimings<TileEntity> data)
{
TileEntity te = data.getObject().get();
if (te == null)
return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.invalid");
String name = getTileEntityName(te);
BlockPos pos = te.getPos();
double averageTimings = data.getAverageTimings();
String tickTime = (averageTimings > 1000 ? TIME_FORMAT.format(averageTimings / 1000) : TIME_FORMAT.format(averageTimings)) + getTimeSuffix(
averageTimings);
return TextComponentHelper
.createComponentTranslation(sender, "commands.forge.tracking.timingEntry", name,
getWorldName(te.getWorld().provider.getDimension()),
pos.getX(), pos.getY(), pos.getZ(), tickTime);
}
private String getTileEntityName(TileEntity tileEntity)
{
ResourceLocation registryId = TileEntity.getKey(tileEntity.getClass());
if (registryId == null)
return tileEntity.getClass().getSimpleName();
else
{
return registryId.toString();
}
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for commands that has subcommands.
* <p>
* E.g. /team settings set [value]
* settings is subcommand of team and set is subcommand of settings
*/
public abstract class CommandTreeBase extends CommandBase
{
private final Map<String, ICommand> commandMap = new HashMap<>();
private final Map<String, ICommand> commandAliasMap = new HashMap<>();
public void addSubcommand(ICommand command)
{
commandMap.put(command.getName(), command);
for (String alias : command.getAliases())
{
commandAliasMap.put(alias, command);
}
}
public Collection<ICommand> getSubCommands()
{
return commandMap.values();
}
@Nullable
public ICommand getSubCommand(String command)
{
ICommand cmd = commandMap.get(command);
if (cmd != null)
{
return cmd;
}
return commandAliasMap.get(command);
}
public Map<String, ICommand> getCommandMap()
{
return Collections.unmodifiableMap(commandMap);
}
public List<ICommand> getSortedCommandList()
{
List<ICommand> list = new ArrayList<>(getSubCommands());
Collections.sort(list);
return list;
}
private static String[] shiftArgs(@Nullable String[] s)
{
if(s == null || s.length == 0)
{
return new String[0];
}
String[] s1 = new String[s.length - 1];
System.arraycopy(s, 1, s1, 0, s1.length);
return s1;
}
/**
* Get a list of options for when the user presses the TAB key
*/
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos pos)
{
if(args.length == 1)
{
List<String> keys = new ArrayList<>();
for (ICommand c : getSubCommands())
{
if(c.checkPermission(server, sender))
{
keys.add(c.getName());
}
}
keys.sort(null);
return getListOfStringsMatchingLastWord(args, keys);
}
ICommand cmd = getSubCommand(args[0]);
if(cmd != null)
{
return cmd.getTabCompletions(server, sender, shiftArgs(args), pos);
}
return super.getTabCompletions(server, sender, args, pos);
}
/**
* Return whether the specified command parameter index is a username parameter.
*/
@Override
public boolean isUsernameIndex(String[] args, int index)
{
if (index > 0 && args.length > 1)
{
ICommand cmd = getSubCommand(args[0]);
if (cmd != null)
{
return cmd.isUsernameIndex(shiftArgs(args), index - 1);
}
}
return false;
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
if (args.length < 1)
{
String subCommandsString = getAvailableSubCommandsString(server, sender);
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.tree_base.available_subcommands", subCommandsString));
}
else
{
ICommand cmd = getSubCommand(args[0]);
if(cmd == null)
{
String subCommandsString = getAvailableSubCommandsString(server, sender);
throw new CommandException("commands.tree_base.invalid_cmd.list_subcommands", args[0], subCommandsString);
}
else if(!cmd.checkPermission(server, sender))
{
throw new CommandException("commands.generic.permission");
}
else
{
cmd.execute(server, sender, shiftArgs(args));
}
}
}
private String getAvailableSubCommandsString(MinecraftServer server, ICommandSender sender)
{
Collection<String> availableCommands = new ArrayList<>();
for (ICommand command : getSubCommands())
{
if (command.checkPermission(server, sender))
{
availableCommands.add(command.getName());
}
}
return CommandBase.joinNiceStringFromCollection(availableCommands);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
/**
* Add help for parent and all its children.
* Must be added to parent after all other commands.
*/
public class CommandTreeHelp extends CommandTreeBase
{
private final ICommand parent;
public CommandTreeHelp(CommandTreeBase parent)
{
this.parent = parent;
for (ICommand command : parent.getSubCommands())
{
addSubcommand(new HelpSubCommand(this, command));
}
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 0;
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "help";
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return "commands.forge.usage.help";
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
if (args.length == 0)
{
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, parent.getUsage(sender)));
for (ICommand subCommand : getSubCommands())
{
if (subCommand instanceof HelpSubCommand && subCommand.checkPermission(server, sender))
{
subCommand.execute(server, sender, args);
}
}
return;
}
super.execute(server, sender, args);
}
public static class HelpSubCommand extends CommandBase
{
private final CommandTreeHelp parent;
private final ICommand command;
public HelpSubCommand(CommandTreeHelp parent, ICommand command)
{
this.parent = parent;
this.command = command;
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 0;
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return command.getName();
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender sender)
{
return command.getUsage(sender);
}
/**
* Check if the given ICommandSender has permission to execute this command
*/
@Override
public boolean checkPermission(MinecraftServer server, ICommandSender sender)
{
return command.checkPermission(server, sender);
}
/**
* Callback for when the command is executed
*/
@Override
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, command.getUsage(sender)));
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.MinecraftServer;
public class ForgeCommand extends CommandTreeBase
{
public ForgeCommand()
{
super.addSubcommand(new CommandTps());
super.addSubcommand(new CommandTrack());
super.addSubcommand(new CommandGenerate());
super.addSubcommand(new CommandEntity());
super.addSubcommand(new CommandSetDimension());
super.addSubcommand(new CommandDimensions());
super.addSubcommand(new CommandTreeHelp(this));
}
/**
* Gets the name of the command
*/
@Override
public String getName()
{
return "forge";
}
@Override
public void addSubcommand(ICommand command)
{
throw new UnsupportedOperationException("Don't add sub-commands to /forge, create your own command.");
}
/**
* Return the required permission level for this command.
*/
@Override
public int getRequiredPermissionLevel()
{
return 0;
}
/**
* Check if the given ICommandSender has permission to execute this command
*/
@Override
public boolean checkPermission(MinecraftServer server, ICommandSender sender)
{
return true;
}
/**
* Gets the usage string for the command.
*/
@Override
public String getUsage(ICommandSender icommandsender)
{
return "commands.forge.usage";
}
}

View File

@@ -0,0 +1,65 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import io.netty.channel.Channel;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.NetworkManager;
import net.minecraft.util.text.TextComponentBase;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.util.text.translation.I18n;
import net.minecraftforge.fml.common.network.NetworkRegistry;
public class TextComponentHelper
{
private TextComponentHelper() {}
/**
* Detects when sending to a vanilla client and falls back to sending english,
* since they don't have the lang data necessary to translate on the client.
*/
public static TextComponentBase createComponentTranslation(ICommandSender sender, final String translation, final Object... args)
{
if (isVanillaClient(sender))
{
return new TextComponentString(I18n.translateToLocalFormatted(translation, args));
}
return new TextComponentTranslation(translation, args);
}
private static boolean isVanillaClient(ICommandSender sender)
{
if (sender instanceof EntityPlayerMP)
{
EntityPlayerMP playerMP = (EntityPlayerMP) sender;
NetHandlerPlayServer connection = playerMP.connection;
if (connection != null)
{
NetworkManager netManager = connection.netManager;
Channel channel = netManager.channel();
return !channel.attr(NetworkRegistry.FML_MARKER).get();
}
}
return false;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.server.command;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View File

@@ -0,0 +1,87 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.console;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.server.dedicated.DedicatedServer;
import org.jline.reader.Candidate;
import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;
final class ConsoleCommandCompleter implements Completer
{
private static final Logger logger = LogManager.getLogger();
private final DedicatedServer server;
public ConsoleCommandCompleter(DedicatedServer server)
{
this.server = checkNotNull(server, "server");
}
@Override
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates)
{
String buffer = line.line();
boolean prefix;
if (buffer.isEmpty() || buffer.charAt(0) != '/')
{
buffer = '/' + buffer;
prefix = false;
}
else
{
prefix = true;
}
final String input = buffer;
Future<List<String>> tabComplete = this.server.callFromMainThread(() -> this.server.getTabCompletions(this.server, input, this.server.getPosition(), false));
try
{
for (String completion : tabComplete.get())
{
if (!completion.isEmpty())
{
boolean hasPrefix = prefix || completion.charAt(0) != '/';
Candidate candidate = new Candidate(hasPrefix ? completion : completion.substring(1));
candidates.add(candidate);
}
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
catch (ExecutionException e)
{
logger.error("Failed to tab complete", e);
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.console;
import net.minecraftforge.server.terminalconsole.TerminalConsoleAppender;
import net.minecraft.server.dedicated.DedicatedServer;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
public final class TerminalHandler
{
private TerminalHandler()
{
}
public static boolean handleCommands(DedicatedServer server)
{
final Terminal terminal = TerminalConsoleAppender.getTerminal();
if (terminal == null)
return false;
LineReader reader = LineReaderBuilder.builder()
.appName("Forge")
.terminal(terminal)
.completer(new ConsoleCommandCompleter(server))
.build();
reader.setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION);
reader.unsetOpt(LineReader.Option.INSERT_TAB);
TerminalConsoleAppender.setReader(reader);
try
{
String line;
while (!server.isServerStopped() && server.isServerRunning())
{
try
{
line = reader.readLine("> ");
}
catch (EndOfFileException ignored)
{
// Continue reading after EOT
continue;
}
if (line == null)
break;
line = line.trim();
if (!line.isEmpty())
{
server.addPendingCommand(line, server);
}
}
}
catch (UserInterruptException e)
{
server.initiateShutdown();
}
finally
{
TerminalConsoleAppender.setReader(null);
}
return true;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.server.permission.context.IContext;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
/**
* Default implementation of PermissionAPI.
* {@link #hasPermission(GameProfile, String, IContext)} is based on DefaultPermissionLevel
*
* @see IPermissionHandler
*/
public enum DefaultPermissionHandler implements IPermissionHandler
{
INSTANCE;
private static final HashMap<String, DefaultPermissionLevel> PERMISSION_LEVEL_MAP = new HashMap<String, DefaultPermissionLevel>();
private static final HashMap<String, String> DESCRIPTION_MAP = new HashMap<String, String>();
@Override
public void registerNode(String node, DefaultPermissionLevel level, String desc)
{
PERMISSION_LEVEL_MAP.put(node, level);
if(!desc.isEmpty())
{
DESCRIPTION_MAP.put(node, desc);
}
}
@Override
public Collection<String> getRegisteredNodes()
{
return Collections.unmodifiableSet(PERMISSION_LEVEL_MAP.keySet());
}
@Override
public boolean hasPermission(GameProfile profile, String node, @Nullable IContext context)
{
DefaultPermissionLevel level = getDefaultPermissionLevel(node);
if(level == DefaultPermissionLevel.NONE)
{
return false;
}
else if(level == DefaultPermissionLevel.ALL)
{
return true;
}
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
return server != null && server.getPlayerList().canSendCommands(profile);
}
@Override
public String getNodeDescription(String node)
{
String desc = DESCRIPTION_MAP.get(node);
return desc == null ? "" : desc;
}
/**
* @return The default permission level of a node. If the permission isn't registred, it will return NONE
*/
public DefaultPermissionLevel getDefaultPermissionLevel(String node)
{
DefaultPermissionLevel level = PERMISSION_LEVEL_MAP.get(node);
return level == null ? DefaultPermissionLevel.NONE : level;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission;
/**
* <table><thead><tr><th>Level</th><th>Player</th><th>OP</th></tr>
* </thead><tbody>
* <tr><td>ALL</td><td>true</td><td>true</td></tr>
* <tr><td>OP</td><td>false</td><td>true</td></tr>
* <tr><td>NONE</td><td>false</td><td>false</td></tr>
* </tbody></table>
*/
public enum DefaultPermissionLevel
{
ALL,
OP,
NONE
}

View File

@@ -0,0 +1,51 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission;
import com.mojang.authlib.GameProfile;
import net.minecraftforge.server.permission.context.IContext;
import javax.annotation.Nullable;
import java.util.Collection;
public interface IPermissionHandler
{
/**
* Use {@link PermissionAPI#registerNode(String, DefaultPermissionLevel, String)}
*/
void registerNode(String node, DefaultPermissionLevel level, String desc);
/**
* @return Immutable collection of all registered nodes
*/
Collection<String> getRegisteredNodes();
/**
* Use {@link PermissionAPI#hasPermission(GameProfile, String, IContext)}
*/
boolean hasPermission(GameProfile profile, String node, @Nullable IContext context);
/**
* @param node Permission node
* @return Description of the node. "" in case this node doesn't have a decription
* @see #registerNode(String, DefaultPermissionLevel, String)
*/
String getNodeDescription(String node);
}

View File

@@ -0,0 +1,97 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission;
import com.google.common.base.Preconditions;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.LoaderState;
import net.minecraftforge.server.permission.context.IContext;
import net.minecraftforge.server.permission.context.PlayerContext;
import org.apache.logging.log4j.Level;
import javax.annotation.Nullable;
public class PermissionAPI
{
private static IPermissionHandler permissionHandler = DefaultPermissionHandler.INSTANCE;
/**
* <b>Only use this in PreInit state!</b>
*/
public static void setPermissionHandler(IPermissionHandler handler)
{
Preconditions.checkNotNull(handler, "Permission handler can't be null!");
Preconditions.checkState(Loader.instance().getLoaderState().ordinal() <= LoaderState.PREINITIALIZATION.ordinal(), "Can't register after IPermissionHandler PreInit!");
FMLLog.log.warn("Replacing {} with {}", permissionHandler.getClass().getName(), handler.getClass().getName());
permissionHandler = handler;
}
public static IPermissionHandler getPermissionHandler()
{
return permissionHandler;
}
/**
* <b>Only use this after PreInit state!</b>
*
* @param node Permission node, best if it's lowercase and contains '.' (e.g. <code>"modid.subgroup.permission_id"</code>)
* @param level Default permission level for this node. If not isn't registered, it's level is going to be 'NONE'
* @param desc Optional description of the node
*/
public static String registerNode(String node, DefaultPermissionLevel level, String desc)
{
Preconditions.checkNotNull(node, "Permission node can't be null!");
Preconditions.checkNotNull(level, "Permission level can't be null!");
Preconditions.checkNotNull(desc, "Permission description can't be null!");
Preconditions.checkArgument(!node.isEmpty(), "Permission node can't be empty!");
Preconditions.checkState(Loader.instance().getLoaderState().ordinal() > LoaderState.PREINITIALIZATION.ordinal(), "Can't register permission nodes before Init!");
permissionHandler.registerNode(node, level, desc);
return node;
}
/**
* @param profile GameProfile of the player who is requesting permission. The player doesn't have to be online
* @param node Permission node. See {@link #registerNode(String, DefaultPermissionLevel, String)}
* @param context Context for this permission. Highly recommended to not be null. See {@link IContext}
* @return true, if player has permission, false if he does not.
* @see DefaultPermissionHandler
*/
public static boolean hasPermission(GameProfile profile, String node, @Nullable IContext context)
{
Preconditions.checkNotNull(profile, "GameProfile can't be null!");
Preconditions.checkNotNull(node, "Permission node can't be null!");
Preconditions.checkArgument(!node.isEmpty(), "Permission node can't be empty!");
return permissionHandler.hasPermission(profile, node, context);
}
/**
* Shortcut method using EntityPlayer and creating PlayerContext
*
* @see PermissionAPI#hasPermission(GameProfile, String, IContext)
*/
public static boolean hasPermission(EntityPlayer player, String node)
{
Preconditions.checkNotNull(player, "Player can't be null!");
return hasPermission(player.getGameProfile(), node, new PlayerContext(player));
}
}

View File

@@ -0,0 +1,50 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import com.google.common.base.Preconditions;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.AxisAlignedBB;
import javax.annotation.Nullable;
public class AreaContext extends PlayerContext
{
private final AxisAlignedBB area;
public AreaContext(EntityPlayer ep, AxisAlignedBB aabb)
{
super(ep);
area = Preconditions.checkNotNull(aabb, "AxisAlignedBB can't be null in AreaContext!");
}
@Override
@Nullable
public <T> T get(ContextKey<T> key)
{
return key.equals(ContextKeys.AREA) ? (T) area : super.get(key);
}
@Override
protected boolean covers(ContextKey<?> key)
{
return key.equals(ContextKeys.AREA);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import com.google.common.base.Preconditions;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import javax.annotation.Nullable;
public class BlockPosContext extends PlayerContext
{
private final BlockPos blockPos;
private IBlockState blockState;
private EnumFacing facing;
public BlockPosContext(EntityPlayer ep, BlockPos pos, @Nullable IBlockState state, @Nullable EnumFacing f)
{
super(ep);
blockPos = Preconditions.checkNotNull(pos, "BlockPos can't be null in BlockPosContext!");
blockState = state;
facing = f;
}
public BlockPosContext(EntityPlayer ep, ChunkPos pos)
{
this(ep, new BlockPos(pos.getXStart() + 8, 0, pos.getZStart() + 8), null, null);
}
@Override
@Nullable
public <T> T get(ContextKey<T> key)
{
if(key.equals(ContextKeys.POS))
{
return (T) blockPos;
}
else if(key.equals(ContextKeys.BLOCK_STATE))
{
if(blockState == null)
{
blockState = getWorld().getBlockState(blockPos);
}
return (T) blockState;
}
else if(key.equals(ContextKeys.FACING))
{
return (T) facing;
}
return super.get(key);
}
@Override
protected boolean covers(ContextKey<?> key)
{
return key.equals(ContextKeys.POS) || key.equals(ContextKeys.BLOCK_STATE) || (facing != null && key.equals(ContextKeys.FACING));
}
}

View File

@@ -0,0 +1,87 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class Context implements IContext
{
private Map<ContextKey<?>, Object> map;
@Override
@Nullable
public World getWorld()
{
return null;
}
@Override
@Nullable
public EntityPlayer getPlayer()
{
return null;
}
@Override
@Nullable
public <T> T get(ContextKey<T> key)
{
return map == null || map.isEmpty() ? null : (T) map.get(key);
}
@Override
public boolean has(ContextKey<?> key)
{
return covers(key) || (map != null && !map.isEmpty() && map.containsKey(key));
}
/**
* Sets Context object
*
* @param key Context key
* @param obj Context object. Can be null
* @return itself, for easy context chaining
*/
public <T> Context set(ContextKey<T> key, @Nullable T obj)
{
if(covers(key))
{
return this;
}
if(map == null)
{
map = new HashMap<ContextKey<?>, Object>();
}
map.put(key, obj);
return this;
}
protected boolean covers(ContextKey<?> key)
{
return false;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import com.google.common.base.Preconditions;
public final class ContextKey<T>
{
private final String ID;
private final Class<T> typeClass;
public static <E> ContextKey<E> create(String id, Class<E> c)
{
Preconditions.checkNotNull(id, "ContextKey's ID can't be null!");
Preconditions.checkNotNull(c, "ContextKey's Type can't be null!");
if(id.isEmpty())
{
throw new IllegalArgumentException("ContextKey's ID can't be blank!");
}
return new ContextKey<E>(id, c);
}
private ContextKey(String id, Class<T> c)
{
ID = id;
typeClass = c;
}
public String toString()
{
return ID;
}
public int hashCode()
{
return ID.hashCode();
}
public boolean equals(Object o)
{
return o == this || (o != null && o.toString().equals(ID));
}
public Class<T> getTypeClass()
{
return typeClass;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
/**
* Some default context keys, for easier compatibility
*/
public class ContextKeys
{
/**
* BlockPos for interacting, breaking and other permissions
*/
public static final ContextKey<BlockPos> POS = ContextKey.create("pos", BlockPos.class);
/**
* The entity can be anything that gets interacted with - a sheep when you try to dye it, skeleton that you attack, etc.
*/
public static final ContextKey<Entity> TARGET = ContextKey.create("target", Entity.class);
public static final ContextKey<EnumFacing> FACING = ContextKey.create("facing", EnumFacing.class);
public static final ContextKey<AxisAlignedBB> AREA = ContextKey.create("area", AxisAlignedBB.class);
public static final ContextKey<IBlockState> BLOCK_STATE = ContextKey.create("blockstate", IBlockState.class);
}

View File

@@ -0,0 +1,56 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import javax.annotation.Nullable;
/**
* Use {@link BlockPosContext} or {@link PlayerContext} when possible
*/
public interface IContext
{
/**
* World from where permission is requested. Can be null
*/
@Nullable
World getWorld();
/**
* @return Player requesting permission. Can be null
*/
@Nullable
EntityPlayer getPlayer();
/**
* @param key Context key
* @return Context object
*/
@Nullable
<T> T get(ContextKey<T> key);
/**
* @param key Context key
* @return true if context contains this key
*/
boolean has(ContextKey<?> key);
}

View File

@@ -0,0 +1,46 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import com.google.common.base.Preconditions;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
public class PlayerContext extends Context
{
private final EntityPlayer player;
public PlayerContext(EntityPlayer ep)
{
player = Preconditions.checkNotNull(ep, "Player can't be null in PlayerContext!");
}
@Override
public World getWorld()
{
return player.getEntityWorld();
}
@Override
public EntityPlayer getPlayer()
{
return player;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import javax.annotation.Nullable;
public class TargetContext extends PlayerContext
{
private final Entity target;
public TargetContext(EntityPlayer ep, @Nullable Entity entity)
{
super(ep);
target = entity;
}
@Override
@Nullable
public <T> T get(ContextKey<T> key)
{
return key.equals(ContextKeys.TARGET) ? (T) target : super.get(key);
}
@Override
protected boolean covers(ContextKey<?> key)
{
return target != null && key.equals(ContextKeys.TARGET);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.permission.context;
import com.google.common.base.Preconditions;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class WorldContext extends Context
{
private final World world;
public WorldContext(World w)
{
world = Preconditions.checkNotNull(w, "World can't be null in WorldContext!");
}
@Override
public World getWorld()
{
return world;
}
@Override
@Nullable
public EntityPlayer getPlayer()
{
return null;
}
}

View File

@@ -0,0 +1,22 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@javax.annotation.ParametersAreNonnullByDefault
@mcp.MethodsReturnNonnullByDefault
package net.minecraftforge.server.permission.context;

View File

@@ -0,0 +1,22 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@javax.annotation.ParametersAreNonnullByDefault
@mcp.MethodsReturnNonnullByDefault
package net.minecraftforge.server.permission;

View File

@@ -0,0 +1,168 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.HighlightConverter;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import java.util.List;
/**
* A simplified version of {@link HighlightConverter} that uses
* {@link TerminalConsoleAppender} to detect if Ansi escape codes can be used
* to highlight errors and warnings in the console.
*
* <p>If configured, it will mark all logged errors with a red color and all
* warnings with a yellow color. It can be only used together with
* {@link TerminalConsoleAppender}.</p>
*
* <p>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} may be used
* to force the use of ANSI colors even in unsupported environments.</p>
*
* <p><b>Example usage:</b> {@code %highlightError{%level: %message}}</p>
*/
@Plugin(name = "highlightError", category = PatternConverter.CATEGORY)
@ConverterKeys({ "highlightError" })
@PerformanceSensitive("allocation")
public class HighlightErrorConverter extends LogEventPatternConverter
{
private static final String ANSI_RESET = "\u001B[39;0m";
private static final String ANSI_ERROR = "\u001B[31;1m";
private static final String ANSI_WARN = "\u001B[33;1m";
private final List<PatternFormatter> formatters;
/**
* Construct the converter.
*
* @param formatters The pattern formatters to generate the text to highlight
*/
protected HighlightErrorConverter(List<PatternFormatter> formatters)
{
super("highlightError", null);
this.formatters = formatters;
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo)
{
if (TerminalConsoleAppender.isAnsiSupported())
{
Level level = event.getLevel();
if (level.isMoreSpecificThan(Level.ERROR))
{
format(ANSI_ERROR, event, toAppendTo);
return;
}
else if (level.isMoreSpecificThan(Level.WARN))
{
format(ANSI_WARN, event, toAppendTo);
return;
}
}
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
}
private void format(String style, LogEvent event, StringBuilder toAppendTo)
{
int start = toAppendTo.length();
toAppendTo.append(style);
int end = toAppendTo.length();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
if (toAppendTo.length() == end)
{
// No content so we don't need to append the ANSI escape code
toAppendTo.setLength(start);
}
else
{
// Append reset code after the line
toAppendTo.append(ANSI_RESET);
}
}
@Override
public boolean handlesThrowable()
{
for (final PatternFormatter formatter : formatters)
{
if (formatter.handlesThrowable())
{
return true;
}
}
return false;
}
/**
* Gets a new instance of the {@link HighlightErrorConverter} with the
* specified options.
*
* @param config The current configuration
* @param options The pattern options
* @return The new instance
*/
@Nullable
public static HighlightErrorConverter newInstance(Configuration config, String[] options)
{
if (options.length != 1)
{
LOGGER.error("Incorrect number of options on highlightError. Expected 1 received " + options.length);
return null;
}
if (options[0] == null)
{
LOGGER.error("No pattern supplied on highlightError");
return null;
}
PatternParser parser = PatternLayout.createPatternParser(config);
List<PatternFormatter> formatters = parser.parse(options[0]);
return new HighlightErrorConverter(formatters);
}
}

View File

@@ -0,0 +1,222 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.PropertiesUtil;
import java.util.List;
/**
* Replaces Minecraft formatting codes in the result of a pattern with
* appropriate ANSI escape codes. The implementation will only replace valid
* color codes using the section sign (§).
*
* <p>The {@link MinecraftFormattingConverter} can be only used together with
* {@link TerminalConsoleAppender} to detect if the current console supports
* color output. When running in an unsupported environment, it will
* automatically strip all formatting codes instead.</p>
*
* <p>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} may be used
* to force the use of ANSI colors even in unsupported environments. As an
* alternative, {@link #KEEP_FORMATTING_PROPERTY} may be used to keep the
* raw Minecraft formatting codes.</p>
*
* <p><b>Example usage:</b> {@code %minecraftFormatting{%message}}<br>
* It can be configured to always strip formatting codes from the message:
* {@code %minecraftFormatting{%message}{strip}}</p>
*
* @see <a href="http://minecraft.gamepedia.com/Formatting_codes">
* Formatting Codes</a>
*/
@Plugin(name = "minecraftFormatting", category = PatternConverter.CATEGORY)
@ConverterKeys({ "minecraftFormatting" })
@PerformanceSensitive("allocation")
public class MinecraftFormattingConverter extends LogEventPatternConverter
{
/**
* System property that allows disabling the replacement of Minecraft
* formatting codes entirely, keeping them in the console output. For
* some applications they might be easier and more accurate for parsing
* in applications like certain control panels.
*
* <p>If this system property is not set, or set to any value except
* {@code true}, all Minecraft formatting codes will be replaced
* or stripped from the console output.</p>
*/
public static final String KEEP_FORMATTING_PROPERTY = TerminalConsoleAppender.PROPERTY_PREFIX + ".keepMinecraftFormatting";
private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY);
private static final String ANSI_RESET = "\u001B[39;0m";
private static final char COLOR_CHAR = '\u00A7'; // §
private static final String LOOKUP = "0123456789abcdefklmnor";
private static final String[] ansiCodes = new String[] {
"\u001B[0;30;22m", // Black §0
"\u001B[0;34;22m", // Dark Blue §1
"\u001B[0;32;22m", // Dark Green §2
"\u001B[0;36;22m", // Dark Aqua §3
"\u001B[0;31;22m", // Dark Red §4
"\u001B[0;35;22m", // Dark Purple §5
"\u001B[0;33;22m", // Gold §6
"\u001B[0;37;22m", // Gray §7
"\u001B[0;30;1m", // Dark Gray §8
"\u001B[0;34;1m", // Blue §9
"\u001B[0;32;1m", // Green §a
"\u001B[0;36;1m", // Aqua §b
"\u001B[0;31;1m", // Red §c
"\u001B[0;35;1m", // Light Purple §d
"\u001B[0;33;1m", // Yellow §e
"\u001B[0;37;1m", // White §f
"\u001B[5m", // Obfuscated §k
"\u001B[21m", // Bold §l
"\u001B[9m", // Strikethrough §m
"\u001B[4m", // Underline §n
"\u001B[3m", // Italic §o
ANSI_RESET, // Reset §r
};
private final boolean ansi;
private final List<PatternFormatter> formatters;
/**
* Construct the converter.
*
* @param formatters The pattern formatters to generate the text to manipulate
* @param strip If true, the converter will strip all formatting codes
*/
protected MinecraftFormattingConverter(List<PatternFormatter> formatters, boolean strip)
{
super("minecraftFormatting", null);
this.formatters = formatters;
this.ansi = !strip;
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo)
{
int start = toAppendTo.length();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
if (KEEP_FORMATTING || toAppendTo.length() == start)
{
// Skip replacement if disabled or if the content is empty
return;
}
String content = toAppendTo.substring(start);
format(content, toAppendTo, start, ansi && TerminalConsoleAppender.isAnsiSupported());
}
private static void format(String s, StringBuilder result, int start, boolean ansi)
{
int next = s.indexOf(COLOR_CHAR);
int last = s.length() - 1;
if (next == -1 || next == last)
{
return;
}
result.setLength(start + next);
int pos = next;
int format;
do {
if (pos != next)
{
result.append(s, pos, next);
}
format = LOOKUP.indexOf(s.charAt(next + 1));
if (format != -1)
{
if (ansi)
{
result.append(ansiCodes[format]);
}
pos = next += 2;
}
else
{
next++;
}
next = s.indexOf(COLOR_CHAR, next);
} while (next != -1 && next < last);
result.append(s, pos, s.length());
if (ansi)
{
result.append(ANSI_RESET);
}
}
/**
* Gets a new instance of the {@link MinecraftFormattingConverter} with the
* specified options.
*
* @param config The current configuration
* @param options The pattern options
* @return The new instance
*
* @see MinecraftFormattingConverter
*/
@Nullable
public static MinecraftFormattingConverter newInstance(Configuration config, String[] options)
{
if (options.length < 1 || options.length > 2)
{
LOGGER.error("Incorrect number of options on minecraftFormatting. Expected at least 1, max 2 received " + options.length);
return null;
}
if (options[0] == null)
{
LOGGER.error("No pattern supplied on minecraftFormatting");
return null;
}
PatternParser parser = PatternLayout.createPatternParser(config);
List<PatternFormatter> formatters = parser.parse(options[0]);
boolean strip = options.length > 1 && "strip".equals(options[1]);
return new MinecraftFormattingConverter(formatters, strip);
}
}

View File

@@ -0,0 +1,374 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.jline.reader.LineReader;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
/**
* An {@link Appender} that uses the JLine 3.x {@link Terminal} to print messages
* to the console.
*
* <p>The JLine {@link Terminal} extends the regular console output with support
* for Ansi escape codes on Windows. Additionally, it's {@link LineReader}
* interface can be used to implement enhanced console input, with an
* persistent input line, as well as command history and command completion.</p>
*
* <p>The {@code TerminalConsole} appender replaces the default {@code Console}
* appender in your log4j configuration. By default, log4j will automatically
* close the standard output when the original {@code Console} appender is
* removed. Consequently, it is necessary to keep an unused {@code Console}
* appender.</p>
*
* <p><b>Example usage:</b></p>
* <pre>{@code <TerminalConsole>
* <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
* </TerminalConsole>
*
* <Console name="SysOut" target="SYSTEM_OUT"/>}</pre>
*
* <p>To use the enhanced console input it is necessary to set the
* {@link LineReader} using {@link #setReader(LineReader)}. The appender will
* then automatically redraw the current prompt. When creating the
* {@link LineReader} it's important to use the {@link Terminal}
* returned by {@link #getTerminal()}. Additionally, the reader should
* be removed from the appender as soon as it's no longer accepting
* input (for example when the user interrupted input using CTRL + C.</p>
*
* <p>By default, the JLine {@link Terminal} is enabled when the application
* is started with an attached terminal session. Usually, this is only the
* case if the application is started from the command line, not if it gets
* started by another application.</p>
*
* <p>In some cases, it might be possible to support a subset of the features
* in these unsupported environments (e.g. only ANSI color codes). In these
* cases, the system properties may be used to override the default behaviour:
* </p>
*
* <ul>
* <li>{@link TerminalConsoleAppender#JLINE_OVERRIDE_PROPERTY} - To enable the extended JLine
* input. By default this will also enable the ANSI escape codes.</li>
* <li>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} - To enable the output of ANSI
* escape codes. May be used to force the use of ANSI escape codes
* if JLine is disabled or to disable them if it is enabled.</li>
* </ul>
*/
@Plugin(name = TerminalConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class TerminalConsoleAppender extends AbstractAppender
{
public static final String PLUGIN_NAME = "TerminalConsole";
/**
* The prefix used for all system properties in TerminalConsoleAppender.
*/
public static final String PROPERTY_PREFIX = "terminal";
/**
* System property that allows overriding the default detection of the
* console to force enable or force disable the use of JLine. In some
* environments the automatic detection might not work properly.
* <p>
* <p>If this system property is not set, or set to an invalid value
* (neither {@code true} nor {@code false}) then we will attempt
* to detect the best option automatically.</p>
*/
public static final String JLINE_OVERRIDE_PROPERTY = PROPERTY_PREFIX + ".jline";
/**
* System property that allows overriding the use of ANSI escape codes
* for console formatting even though running in an unsupported
* environment. By default, ANSI color codes are only enabled if JLine
* is enabled. Some systems might be able to handle ANSI escape codes
* but are not capable of JLine's extended input mechanism.
* <p>
* <p>If this system property is not set, or set to an invalid value
* (neither {@code true} nor {@code false}) then we will attempt
* to detect the best option automatically.</p>
*/
public static final String ANSI_OVERRIDE_PROPERTY = PROPERTY_PREFIX + ".ansi";
public static final Boolean ANSI_OVERRIDE = getOptionalBooleanProperty(ANSI_OVERRIDE_PROPERTY);
/**
* We grab the standard output {@link PrintStream} early, otherwise we
* might cause infinite loops later if the application redirects
* {@link System#out} to Log4J.
*/
private static final PrintStream stdout = System.out;
private static boolean initialized;
@Nullable
private static Terminal terminal;
@Nullable
private static LineReader reader;
/**
* Returns the {@link Terminal} that is used to print messages to the
* console. Returns {@code null} in unsupported environments, unless
* overridden using the {@link TerminalConsoleAppender#JLINE_OVERRIDE_PROPERTY} system
* property.
*
* @return The terminal, or null if not supported
* @see TerminalConsoleAppender
*/
@Nullable
public static Terminal getTerminal()
{
return terminal;
}
/**
* Returns the currently configured {@link LineReader} that is used to
* read input from the console. May be null if no {@link LineReader}
* was configured by the environment.
*
* @return The current line reader, or null if none
*/
@Nullable
public static LineReader getReader()
{
return reader;
}
/**
* Sets the {@link LineReader} that is used to read input from the console.
* Setting the {@link LineReader} will allow the appender to automatically
* redraw the input line when a new log message is added.
*
* <p><b>Note:</b> The specified {@link LineReader} must be created with
* the terminal returned by {@link #getTerminal()}.</p>
*
* @param newReader The new line reader
*/
public static void setReader(@Nullable LineReader newReader)
{
if (newReader != null && newReader.getTerminal() != terminal)
{
throw new IllegalArgumentException("Reader was not created with TerminalConsoleAppender.getTerminal()");
}
reader = newReader;
}
/**
* Returns whether ANSI escapes codes should be written to the console
* output.
*
* <p>The return value is {@code true} by default if the JLine terminal
* is enabled and {@code false} otherwise. It may be overridden using
* the {@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} system property.</p>
*
* @return true if ANSI escapes codes should be written to the console
*/
public static boolean isAnsiSupported()
{
return ANSI_OVERRIDE != null ? ANSI_OVERRIDE : terminal != null;
}
/**
* Constructs a new {@link TerminalConsoleAppender}.
*
* @param name The name of the appender
* @param filter The filter, can be {@code null}
* @param layout The layout to use
* @param ignoreExceptions If {@code true} exceptions encountered when
* appending events are logged, otherwise they are propagated to the
* caller
*/
protected TerminalConsoleAppender(String name, @Nullable Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions)
{
super(name, filter, layout, ignoreExceptions);
initializeTerminal();
}
private static void initializeTerminal()
{
if (!initialized)
{
initialized = true;
// A system property can be used to override our automatic detection
Boolean jlineOverride = getOptionalBooleanProperty(JLINE_OVERRIDE_PROPERTY);
// By default, we disable JLine if there is no terminal attached
// (e.g. if the program output is redirected to a file or if it's
// started by some kind of control panel)
// The same applies to IDEs, they usually provide only a very basic
// console implementation without support for ANSI escape codes
// (used for colors) or characters like \r.
// There are two exceptions:
// 1. IntelliJ IDEA supports colors and control characters
// (We try to detect it using an additional JAR it adds to the classpath)
// 2. The system property forces the use of JLine.
boolean dumb = jlineOverride == Boolean.TRUE || System.getProperty("java.class.path").contains("idea_rt.jar");
if (jlineOverride != Boolean.FALSE)
{
try
{
terminal = TerminalBuilder.builder().dumb(dumb).build();
}
catch (IllegalStateException e)
{
// Unless disabled using one of the exceptions above,
// JLine throws an exception before creating a dumb terminal
// Dumb terminals are used if there is no real terminal attached
// to the application.
if (LOGGER.isDebugEnabled())
{
// Log with stacktrace
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.", e);
}
else
{
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.");
}
}
catch (IOException e)
{
LOGGER.error("Failed to initialize terminal. Falling back to standard output", e);
}
}
}
}
@Nullable
private static Boolean getOptionalBooleanProperty(String name)
{
String value = PropertiesUtil.getProperties().getStringProperty(name);
if (value == null)
{
return null;
}
if (value.equalsIgnoreCase("true"))
{
return Boolean.TRUE;
}
else if (value.equalsIgnoreCase("false"))
{
return Boolean.FALSE;
}
else
{
LOGGER.warn("Invalid value for boolean input property '{}': {}", name, value);
return null;
}
}
@Override
public void append(LogEvent event)
{
if (terminal != null)
{
if (reader != null)
{
// Draw the prompt line again if a reader is available
reader.callWidget(LineReader.CLEAR);
terminal.writer().print(getLayout().toSerializable(event));
reader.callWidget(LineReader.REDRAW_LINE);
reader.callWidget(LineReader.REDISPLAY);
}
else
{
terminal.writer().print(getLayout().toSerializable(event));
}
terminal.writer().flush();
}
else
{
stdout.print(getLayout().toSerializable(event));
}
}
/**
* Closes the JLine {@link Terminal} (if available) and restores the original
* terminal settings.
*
* @throws IOException If an I/O error occurs
*/
public static void close() throws IOException
{
if (initialized)
{
initialized = false;
if (terminal != null)
{
try
{
terminal.close();
}
finally
{
terminal = null;
}
}
}
}
/**
* Creates a new {@link TerminalConsoleAppender}.
*
* @param name The name of the appender
* @param filter The filter, can be {@code null}
* @param layout The layout, can be {@code null}
* @param ignoreExceptions If {@code true} exceptions encountered when
* appending events are logged, otherwise they are propagated to the
* caller
* @return The new appender
*/
@PluginFactory
public static TerminalConsoleAppender createAppender(
@Required(message = "No name provided for TerminalConsoleAppender") @PluginAttribute("name") String name,
@PluginElement("Filter") @Nullable Filter filter,
@PluginElement("Layout") @Nullable Layout<? extends Serializable> layout,
@PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) boolean ignoreExceptions)
{
if (layout == null)
{
layout = PatternLayout.createDefaultLayout();
}
return new TerminalConsoleAppender(name, filter, layout, ignoreExceptions);
}
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.server.terminalconsole;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View File

@@ -0,0 +1,171 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole.util;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.layout.PatternMatch;
import org.apache.logging.log4j.core.layout.PatternSelector;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link PatternSelector} that selects patterns based on the logger name.
* Can be used to log messages from different loggers using different patterns.
*
* <p>Multiple logger names may be separated using comma in the
* {@link PatternMatch#getKey() PatternMatch "key"}. The pattern will be applied
* if the logger name matches at least one of them.</p>
*
* <p><b>Example usage:</b></p>
* <pre>{@code <PatternLayout>
* <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss} %level] [%logger]: %msg%n">
* <!-- Log root (empty logger name), "Main", and net.minecrell.* without logger prefix -->
* <PatternMatch key=",Main,net.minecrell." pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
* <PatternMatch key="com.example.Logger" pattern="EXAMPLE: %msg%n"/>
* </LoggerNamePatternSelector>
* </PatternLayout>}</pre>
*/
@Plugin(name = "LoggerNamePatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE)
@PerformanceSensitive("allocation")
public class LoggerNamePatternSelector implements PatternSelector
{
private static class LoggerNameSelector
{
private final String name;
private final boolean isPackage;
private final PatternFormatter[] formatters;
LoggerNameSelector(String name, PatternFormatter[] formatters)
{
this.name = name;
this.isPackage = name.endsWith(".");
this.formatters = formatters;
}
PatternFormatter[] get()
{
return this.formatters;
}
boolean test(String s)
{
return this.isPackage ? s.startsWith(this.name) : s.equals(this.name);
}
}
private final PatternFormatter[] defaultFormatters;
private final List<LoggerNameSelector> formatters = new ArrayList<>();
/**
* Constructs a new {@link LoggerNamePatternSelector}.
*
* @param defaultPattern The default pattern to use if no logger name matches
* @param properties The pattern match rules to use
* @param alwaysWriteExceptions Write exceptions even if pattern does not
* include exception conversion
* @param disableAnsi If true, disable all ANSI escape codes
* @param noConsoleNoAnsi If true and {@link System#console()} is null,
* disable ANSI escape codes
* @param config The configuration
*/
protected LoggerNamePatternSelector(String defaultPattern, PatternMatch[] properties,
boolean alwaysWriteExceptions, boolean disableAnsi, boolean noConsoleNoAnsi, Configuration config)
{
PatternParser parser = PatternLayout.createPatternParser(config);
this.defaultFormatters = toArray(parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi));
for (PatternMatch property : properties)
{
PatternFormatter[] formatters = toArray(parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi));
for (String name : property.getKey().split(","))
{
this.formatters.add(new LoggerNameSelector(name, formatters));
}
}
}
private static PatternFormatter[] toArray(List<PatternFormatter> formatters)
{
return formatters.toArray(new PatternFormatter[formatters.size()]);
}
@Override
public PatternFormatter[] getFormatters(LogEvent event)
{
final String loggerName = event.getLoggerName();
if (loggerName != null)
{
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < this.formatters.size(); i++)
{
LoggerNameSelector selector = this.formatters.get(i);
if (selector.test(loggerName))
{
return selector.get();
}
}
}
return this.defaultFormatters;
}
/**
* Creates a new {@link LoggerNamePatternSelector}.
*
* @param defaultPattern The default pattern to use if no logger name matches
* @param properties The pattern match rules to use
* @param alwaysWriteExceptions Write exceptions even if pattern does not
* include exception conversion
* @param disableAnsi If true, disable all ANSI escape codes
* @param noConsoleNoAnsi If true and {@link System#console()} is null,
* disable ANSI escape codes
* @param config The configuration
* @return The new pattern selector
*/
@PluginFactory
public static LoggerNamePatternSelector createSelector(
@Required(message = "Default pattern is required") @PluginAttribute(value = "defaultPattern") String defaultPattern,
@PluginElement("PatternMatch") PatternMatch[] properties,
@PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) boolean alwaysWriteExceptions,
@PluginAttribute("disableAnsi") boolean disableAnsi,
@PluginAttribute("noConsoleNoAnsi") boolean noConsoleNoAnsi,
@PluginConfiguration Configuration config)
{
return new LoggerNamePatternSelector(defaultPattern, properties, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, config);
}
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.server.terminalconsole.util;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View File

@@ -0,0 +1,80 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.timings;
import java.lang.ref.WeakReference;
import java.util.Arrays;
/**
* ForgeTimings aggregates timings data collected by {@link TimeTracker} for an Object
* and performs operations for interpretation of the data.
*
* @param <T>
*/
public class ForgeTimings<T>
{
private WeakReference<T> object;
private int[] rawTimingData;
public ForgeTimings(T object, int[] rawTimingData)
{
this.object = new WeakReference<T>(object);
this.rawTimingData = rawTimingData;
}
/**
* Retrieves the object that the timings are for
*
* @return The object
*/
public WeakReference<T> getObject()
{
return object;
}
/**
* Averages the raw timings data collected
*
* @return An average of the raw timing data
*/
public double getAverageTimings()
{
double sum = 0.0;
for (int data : rawTimingData)
{
sum += data;
}
return sum / rawTimingData.length;
}
/**
* Returns a copy of the raw timings data collected by the tracker
* @return The raw timing data
* @deprecated Added for compatibility, remove in 1.13
*/
public int[] getRawTimingData(){
return Arrays.copyOfRange(rawTimingData, 0, rawTimingData.length);
}
}

View File

@@ -0,0 +1,145 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.timings;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.MapMaker;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
/**
* A class to assist in the collection of data to measure the update times of ticking objects {currently Tile Entities and Entities}
*
* @param <T>
*/
public class TimeTracker<T>
{
/**
* A tracker for timing tile entity update
*/
public static final TimeTracker<TileEntity> TILE_ENTITY_UPDATE = new TimeTracker<>();
/**
* A tracker for timing entity updates
*/
public static final TimeTracker<Entity> ENTITY_UPDATE = new TimeTracker<>();
private boolean enabled;
private int trackingDuration;
private Map<T, int[]> timings = new MapMaker().weakKeys().makeMap();
private WeakReference<T> currentlyTracking;
private long trackTime;
private long timing;
/**
* Returns the timings data recorded by the tracker
*
* @return An immutable list of timings data collected by this tracker
*/
public ImmutableList<ForgeTimings<T>> getTimingData()
{
ImmutableList.Builder<ForgeTimings<T>> builder = ImmutableList.builder();
for (Map.Entry<T, int[]> entry : timings.entrySet())
{
builder.add(new ForgeTimings<>(entry.getKey(), Arrays.copyOfRange(entry.getValue(), 0, 99)));
}
return builder.build();
}
/**
* Resets the tracker (clears timings and stops any in-progress timings)
*/
public void reset()
{
enabled = false;
trackTime = 0;
timings.clear();
}
/**
* Ends the timing of the currently tracking object
*
* @param tracking The object to stop timing
*/
public void trackEnd(T tracking)
{
if (!enabled)
return;
this.trackEnd(tracking, System.nanoTime());
}
/**
* Starts recording tracking data for the given duration in seconds
*
* @param duration The duration for the time to track
*/
public void enable(int duration)
{
this.trackingDuration = duration;
this.enabled = true;
}
/**
* Starts timing of the provided object
*
* @param toTrack The object to start timing
*/
public void trackStart(T toTrack)
{
if (!enabled)
return;
this.trackStart(toTrack, System.nanoTime());
}
private void trackEnd(T object, long nanoTime)
{
if (currentlyTracking == null || currentlyTracking.get() != object)
{
currentlyTracking = null;
return;
}
int[] timings = this.timings.computeIfAbsent(object, k -> new int[101]);
int idx = timings[100] = (timings[100] + 1) % 100;
timings[idx] = (int) (nanoTime - timing);
}
private void trackStart(T toTrack, long nanoTime)
{
if (trackTime == 0)
{
trackTime = nanoTime;
}
else if (trackTime + TimeUnit.NANOSECONDS.convert(trackingDuration, TimeUnit.SECONDS) < nanoTime)
{
enabled = false;
trackTime = 0;
}
currentlyTracking = new WeakReference<>(toTrack);
timing = nanoTime;
}
}