1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-10 17:30:29 +00:00
CC-Tweaked/src/main/java/dan200/computercraft/ComputerCraft.java

668 lines
23 KiB
Java
Raw Normal View History

/*
* This file is part of ComputerCraft - http://www.computercraft.info
2020-01-01 00:09:18 +00:00
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.items.ItemCommandComputer;
import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.media.items.ItemDiskExpanded;
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
import dan200.computercraft.shared.peripheral.common.ItemPeripheral;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wired.ItemCable;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.wireless.ItemAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
Backport 1.15's terminal rendering code (#412) This is a backport of 1.15's terminal rendering code with some further improvements. This duplicates a fair bit of code, and is much more efficient. I expect the work done in #409 will supersede this, but that's unlikely to make its way into the next release so it's worth getting this in for now. - Refactor a lot of common terminal code into `FixedWithFontRenderer`. This shouldn't change any behaviour, but makes a lot of our terminal renderers (printed pages, terminals, monitors) a lot cleaner. - Terminal rendering is done using a single mode/vertex format. Rather than drawing an untextured quad for the background colours, we use an entirely white piece of the terminal font. This allows us to batch draws together more elegantly. - Some minor optimisations: - Skip rendering `"\0"` and `" "` characters. These characters occur pretty often, especially on blank monitors and, as the font is empty here, it is safe to skip them. - Batch together adjacent background cells of the same colour. Again, most terminals will have large runs of the same colour, so this is a worthwhile optimisation. These optimisations do mean that terminal performance is no longer consistent as "noisy" terminals will have worse performance. This is annoying, but still worthwhile. - Switch monitor rendering over to use VBOs. We also add a config option to switch between rendering backends. By default we'll choose the best one compatible with your GPU, but there is a config option to switch between VBOS (reasonable performance) and display lists (bad). When benchmarking 30 full-sized monitors rendering a static image, this improves my FPS[^1] from 7 to 95. This is obviously an extreme case - monitor updates are still slow, and so more frequently updating screens will still be less than stellar. [^1]: My graphics card is an Intel HD Graphics 520. Obviously numbers will vary.
2020-04-21 09:43:26 +00:00
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.items.ItemTurtleAdvanced;
import dan200.computercraft.shared.turtle.items.ItemTurtleLegacy;
import dan200.computercraft.shared.turtle.items.ItemTurtleNormal;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.CreativeTabMain;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.IoUtil;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.fml.common.*;
import net.minecraftforge.fml.common.event.*;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Mod(
modid = ComputerCraft.MOD_ID, name = ComputerCraft.NAME, version = ComputerCraft.VERSION,
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory",
dependencies = "required:forge@[14.23.4.2746,)"
)
public class ComputerCraft
{
2017-05-11 00:08:26 +00:00
public static final String MOD_ID = "computercraft";
static final String VERSION = "${version}";
static final String NAME = "CC: Tweaked";
2017-05-07 00:42:32 +00:00
2017-05-01 14:48:44 +00:00
// Configuration options
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
public static final String[] DEFAULT_HTTP_BLACKLIST = new String[] {
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
};
public static int computerSpaceLimit = 1000 * 1000;
public static int floppySpaceLimit = 125 * 1000;
public static int maximumFilesOpen = 128;
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static boolean logPeripheralErrors = true;
public static boolean commandRequireCreative = true;
public static int computer_threads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
public static int httpTimeout = 30000;
public static int httpMaxRequests = 16;
public static long httpMaxDownload = 16 * 1024 * 1024;
public static long httpMaxUpload = 4 * 1024 * 1024;
public static int httpMaxWebsockets = 4;
public static int httpMaxWebsocketMessage = 128 * 1024;
2017-05-01 14:48:44 +00:00
public static boolean enableCommandBlock = false;
public static int modem_range = 64;
public static int modem_highAltitudeRange = 384;
public static int modem_rangeDuringStorm = 64;
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
Backport 1.15's terminal rendering code (#412) This is a backport of 1.15's terminal rendering code with some further improvements. This duplicates a fair bit of code, and is much more efficient. I expect the work done in #409 will supersede this, but that's unlikely to make its way into the next release so it's worth getting this in for now. - Refactor a lot of common terminal code into `FixedWithFontRenderer`. This shouldn't change any behaviour, but makes a lot of our terminal renderers (printed pages, terminals, monitors) a lot cleaner. - Terminal rendering is done using a single mode/vertex format. Rather than drawing an untextured quad for the background colours, we use an entirely white piece of the terminal font. This allows us to batch draws together more elegantly. - Some minor optimisations: - Skip rendering `"\0"` and `" "` characters. These characters occur pretty often, especially on blank monitors and, as the font is empty here, it is safe to skip them. - Batch together adjacent background cells of the same colour. Again, most terminals will have large runs of the same colour, so this is a worthwhile optimisation. These optimisations do mean that terminal performance is no longer consistent as "noisy" terminals will have worse performance. This is annoying, but still worthwhile. - Switch monitor rendering over to use VBOs. We also add a config option to switch between rendering backends. By default we'll choose the best one compatible with your GPU, but there is a config option to switch between VBOS (reasonable performance) and display lists (bad). When benchmarking 30 full-sized monitors rendering a static image, this improves my FPS[^1] from 7 to 95. This is obviously an extreme case - monitor updates are still slow, and so more frequently updating screens will still be less than stellar. [^1]: My graphics card is an Intel HD Graphics 520. Obviously numbers will vary.
2020-04-21 09:43:26 +00:00
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static long monitorBandwidth = 1_000_000;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;
public static int advancedTurtleFuelLimit = 100000;
public static boolean turtlesObeyBlockProtection = true;
public static boolean turtlesCanPush = true;
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
public static final int terminalWidth_computer = 51;
2017-05-01 14:48:44 +00:00
public static final int terminalHeight_computer = 19;
public static final int terminalWidth_turtle = 39;
public static final int terminalHeight_turtle = 13;
public static final int terminalWidth_pocketComputer = 26;
public static final int terminalHeight_pocketComputer = 20;
// Blocks and Items
public static final class Blocks
2017-05-01 14:48:44 +00:00
{
public static BlockComputer computer;
public static BlockCommandComputer commandComputer;
public static BlockTurtle turtle;
public static BlockTurtle turtleExpanded;
public static BlockTurtle turtleAdvanced;
public static BlockPeripheral peripheral;
public static BlockCable cable;
public static BlockAdvancedModem advancedModem;
public static BlockWiredModemFull wiredModemFull;
}
public static final class Items
2017-05-01 14:48:44 +00:00
{
public static ItemComputer computer;
public static ItemCommandComputer commandComputer;
public static ItemTurtleLegacy turtle;
public static ItemTurtleNormal turtleExpanded;
public static ItemTurtleAdvanced turtleAdvanced;
public static ItemPocketComputer pocketComputer;
2017-05-01 14:48:44 +00:00
public static ItemDiskLegacy disk;
public static ItemDiskExpanded diskExpanded;
public static ItemTreasureDisk treasureDisk;
public static ItemPrintout printout;
public static ItemPeripheral peripheral;
public static ItemAdvancedModem advancedModem;
public static ItemCable cable;
public static ItemBlock wiredModemFull;
2017-05-01 14:48:44 +00:00
}
public static final class TurtleUpgrades
{
public static TurtleModem wirelessModem;
public static TurtleModem advancedModem;
public static TurtleSpeaker speaker;
public static TurtleCraftingTable craftingTable;
public static TurtleSword diamondSword;
public static TurtleShovel diamondShovel;
public static TurtleTool diamondPickaxe;
public static TurtleAxe diamondAxe;
public static TurtleHoe diamondHoe;
}
public static final class PocketUpgrades
{
public static PocketModem wirelessModem;
public static PocketModem advancedModem;
public static PocketSpeaker speaker;
@Deprecated
public static PocketSpeaker pocketSpeaker;
}
@Deprecated
public static final class Upgrades
{
public static TurtleModem advancedModem;
}
// Registries
public static final ClientComputerRegistry clientComputerRegistry = new ClientComputerRegistry();
public static final ServerComputerRegistry serverComputerRegistry = new ServerComputerRegistry();
2017-05-01 14:48:44 +00:00
// Creative
public static CreativeTabMain mainCreativeTab;
// Logging
public static Logger log;
// Peripheral providers. This is still here to ensure compatibility with Plethora and Computronics
public static List<IPeripheralProvider> peripheralProviders = new ArrayList<>();
// Implementation
@Mod.Instance( ComputerCraft.MOD_ID )
2017-05-01 14:48:44 +00:00
public static ComputerCraft instance;
@SidedProxy(
clientSide = "dan200.computercraft.client.proxy.ComputerCraftProxyClient",
serverSide = "dan200.computercraft.shared.proxy.ComputerCraftProxyCommon"
)
private static ComputerCraftProxyCommon proxy;
2017-05-01 14:48:44 +00:00
@Mod.EventHandler
public void preInit( FMLPreInitializationEvent event )
{
log = event.getModLog();
// Load config
Config.load( event.getSuggestedConfigurationFile() );
proxy.preInit();
}
2017-05-01 14:48:44 +00:00
@Mod.EventHandler
public void init( FMLInitializationEvent event )
{
proxy.init();
}
@Mod.EventHandler
public void onServerStarting( FMLServerStartingEvent event )
{
ComputerCraftProxyCommon.initServer( event.getServer() );
}
@Mod.EventHandler
public void onServerStart( FMLServerStartedEvent event )
{
if( FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}
@Mod.EventHandler
public void onServerStopped( FMLServerStoppedEvent event )
{
if( FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
MainThread.reset();
Tracking.reset();
}
}
public static String getVersion()
{
return VERSION;
}
private static File getBaseDir()
2017-05-01 14:48:44 +00:00
{
return FMLCommonHandler.instance().getMinecraftServerInstance().getDataDirectory();
2017-05-01 14:48:44 +00:00
}
private static File getResourcePackDir()
2017-05-01 14:48:44 +00:00
{
return new File( getBaseDir(), "resourcepacks" );
}
@Deprecated
public static void registerPermissionProvider( ITurtlePermissionProvider provider )
{
TurtlePermissions.register( provider );
}
@Deprecated
public static void registerPocketUpgrade( IPocketUpgrade upgrade )
{
dan200.computercraft.shared.PocketUpgrades.register( upgrade );
}
@Deprecated
public static void registerPeripheralProvider( IPeripheralProvider provider )
2017-05-01 14:48:44 +00:00
{
Peripherals.register( provider );
2017-05-01 14:48:44 +00:00
}
@Deprecated
public static void registerBundledRedstoneProvider( IBundledRedstoneProvider provider )
{
BundledRedstone.register( provider );
}
@Deprecated
public static void registerMediaProvider( IMediaProvider provider )
{
MediaProviders.register( provider );
}
@Deprecated
public static void registerAPIFactory( ILuaAPIFactory factory )
{
ApiFactories.register( factory );
}
@Deprecated
public static IWiredNode createWiredNodeForElement( IWiredElement element )
{
return new WiredNode( element );
}
@Deprecated
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
{
TileEntity tile = world.getTileEntity( pos );
return tile != null && tile.hasCapability( CapabilityWiredElement.CAPABILITY, side )
? tile.getCapability( CapabilityWiredElement.CAPABILITY, side )
: null;
}
@Deprecated
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
{
return BundledRedstone.getDefaultOutput( world, pos, side );
}
@Deprecated
public static IPacketNetwork getWirelessNetwork()
{
return WirelessNetwork.getUniversal();
}
@Deprecated
2017-05-01 14:48:44 +00:00
public static int createUniqueNumberedSaveDir( World world, String parentSubPath )
{
return IDAssigner.getNextIDFromDirectory( parentSubPath );
2017-05-01 14:48:44 +00:00
}
@Deprecated
2017-05-01 14:48:44 +00:00
public static IWritableMount createSaveDirMount( World world, String subPath, long capacity )
{
try
{
return new FileMount( new File( getWorldDir(), subPath ), capacity );
2017-05-01 14:48:44 +00:00
}
catch( Exception e )
{
return null;
}
}
private static void loadFromFile( List<IMount> mounts, File file, String path, boolean allowMissing )
{
try
{
if( file.isFile() )
{
mounts.add( new JarMount( file, path ) );
}
else
{
File subResource = new File( file, path );
if( subResource.exists() ) mounts.add( new FileMount( subResource, 0 ) );
}
}
catch( IOException | RuntimeException e )
{
if( allowMissing && e instanceof FileNotFoundException ) return;
ComputerCraft.log.error( "Could not load mount '" + path + " 'from '" + file.getName() + "'", e );
}
}
@Deprecated
2017-05-06 23:07:42 +00:00
public static IMount createResourceMount( Class<?> modClass, String domain, String subPath )
2017-05-01 14:48:44 +00:00
{
// Start building list of mounts
List<IMount> mounts = new ArrayList<>();
subPath = "assets/" + domain + "/" + subPath;
// Mount from debug dir
File codeDir = getDebugCodeDir( modClass );
if( codeDir != null )
{
File subResource = new File( codeDir, subPath );
if( subResource.exists() )
{
IMount resourcePackMount = new FileMount( subResource, 0 );
mounts.add( resourcePackMount );
}
}
// Mount from mod jars, preferring the specified one.
File modJar = getContainingJar( modClass );
Set<File> otherMods = new HashSet<>();
for( ModContainer container : Loader.instance().getActiveModList() )
{
File modFile = container.getSource();
if( modFile != null && !modFile.equals( modJar ) && modFile.exists() )
{
otherMods.add( container.getSource() );
}
}
for( File file : otherMods )
{
loadFromFile( mounts, file, subPath, true );
}
if( modJar != null )
{
loadFromFile( mounts, modJar, subPath, false );
}
// Mount from resource packs
File resourcePackDir = getResourcePackDir();
if( resourcePackDir.exists() && resourcePackDir.isDirectory() )
{
String[] resourcePacks = resourcePackDir.list();
2018-10-29 15:59:43 +00:00
for( String resourcePackName : resourcePacks )
{
File resourcePack = new File( resourcePackDir, resourcePackName );
loadFromFile( mounts, resourcePack, subPath, true );
}
}
// Return the combination of all the mounts found
if( mounts.size() >= 2 )
{
IMount[] mountArray = new IMount[mounts.size()];
mounts.toArray( mountArray );
return new ComboMount( mountArray );
}
else if( mounts.size() == 1 )
{
return mounts.get( 0 );
}
else
{
return null;
}
2017-05-01 14:48:44 +00:00
}
public static InputStream getResourceFile( Class<?> modClass, String domain, String subPath )
{
// Start searching in possible locations
subPath = "assets/" + domain + "/" + subPath;
// Look in resource packs
File resourcePackDir = getResourcePackDir();
if( resourcePackDir.exists() && resourcePackDir.isDirectory() )
{
String[] resourcePacks = resourcePackDir.list();
for( String resourcePackPath : resourcePacks )
{
File resourcePack = new File( resourcePackDir, resourcePackPath );
if( resourcePack.isDirectory() )
{
// Mount a resource pack from a folder
File subResource = new File( resourcePack, subPath );
if( subResource.exists() && subResource.isFile() )
{
try
{
return new FileInputStream( subResource );
}
catch( FileNotFoundException ignored )
{
}
}
}
else
{
ZipFile zipFile = null;
try
{
final ZipFile zip = zipFile = new ZipFile( resourcePack );
ZipEntry entry = zipFile.getEntry( subPath );
if( entry != null )
{
// Return a custom InputStream which will close the original zip when finished.
return new FilterInputStream( zipFile.getInputStream( entry ) )
{
@Override
public void close() throws IOException
{
super.close();
zip.close();
}
};
}
else
{
IoUtil.closeQuietly( zipFile );
}
}
catch( IOException e )
{
IoUtil.closeQuietly( zipFile );
}
}
}
}
// Look in debug dir
File codeDir = getDebugCodeDir( modClass );
if( codeDir != null )
{
File subResource = new File( codeDir, subPath );
if( subResource.exists() && subResource.isFile() )
{
try
{
return new FileInputStream( subResource );
}
catch( FileNotFoundException ignored )
{
}
}
}
// Look in class loader
return modClass.getClassLoader().getResourceAsStream( subPath );
}
2017-05-06 23:07:42 +00:00
private static File getContainingJar( Class<?> modClass )
2017-05-01 14:48:44 +00:00
{
String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( '!' );
2017-05-01 14:48:44 +00:00
if( bangIndex >= 0 )
{
path = path.substring( 0, bangIndex );
}
URL url;
try
{
2017-05-01 14:48:44 +00:00
url = new URL( path );
}
catch( MalformedURLException e1 )
{
2017-05-01 14:48:44 +00:00
return null;
}
File file;
try
{
2017-05-01 14:48:44 +00:00
file = new File( url.toURI() );
}
catch( URISyntaxException e )
{
2017-05-01 14:48:44 +00:00
file = new File( url.getPath() );
}
return file;
}
2017-05-06 23:07:42 +00:00
private static File getDebugCodeDir( Class<?> modClass )
{
String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( '!' );
return bangIndex >= 0 ? null : new File( new File( path ).getParentFile(), "../.." );
}
@Deprecated
public static void registerTurtleUpgrade( ITurtleUpgrade upgrade )
{
dan200.computercraft.shared.TurtleUpgrades.register( upgrade );
}
public static File getWorldDir()
{
return DimensionManager.getCurrentSaveRootDirectory();
}
//region Compatibility
@Deprecated
public static File getWorldDir( World world )
{
return DimensionManager.getCurrentSaveRootDirectory();
}
@Deprecated
public static IMedia getMedia( ItemStack stack )
{
return MediaProviders.get( stack );
}
@Deprecated
public static IPocketUpgrade getPocketUpgrade( ItemStack stack )
{
return dan200.computercraft.shared.PocketUpgrades.get( stack );
}
@Deprecated
public static ITurtleUpgrade getTurtleUpgrade( ItemStack stack )
{
return dan200.computercraft.shared.TurtleUpgrades.get( stack );
}
@Deprecated
public static IPocketUpgrade getPocketUpgrade( String id )
{
return dan200.computercraft.shared.PocketUpgrades.get( id );
}
@Deprecated
public static ITurtleUpgrade getTurtleUpgrade( String id )
{
return dan200.computercraft.shared.TurtleUpgrades.get( id );
}
@Deprecated
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
{
return Peripherals.getPeripheral( world, pos, side );
}
@Deprecated
public static boolean canPlayerUseCommands( EntityPlayer player )
{
MinecraftServer server = player.getServer();
return server != null && server.getPlayerList().canSendCommands( player.getGameProfile() );
}
//endregion
}