mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-12 18:30:29 +00:00
Merge branch 'master' into textcheck
This commit is contained in:
commit
845e1b633d
2
LICENSE
2
LICENSE
@ -36,7 +36,7 @@ This mod is provided 'as is' with no warranties, implied or otherwise. The owner
|
||||
of this mod takes no responsibility for any damages incurred from the use of
|
||||
this mod. This mod alters fundamental parts of the Minecraft game, parts of
|
||||
Minecraft may not work with this mod installed. All damages caused from the use
|
||||
or misuse of this mad fall on the user.
|
||||
or misuse of this mod fall on the user.
|
||||
|
||||
3. Play rights
|
||||
--------------
|
||||
|
@ -18,6 +18,7 @@ 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.core.apis.AddressPredicate;
|
||||
import dan200.computercraft.core.filesystem.ComboMount;
|
||||
import dan200.computercraft.core.filesystem.FileMount;
|
||||
import dan200.computercraft.core.filesystem.JarMount;
|
||||
@ -60,6 +61,7 @@ import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.config.ConfigCategory;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import net.minecraftforge.common.config.Property;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
@ -105,8 +107,18 @@ public class ComputerCraft
|
||||
public static final int pocketComputerGUIID = 106;
|
||||
|
||||
// Configuration options
|
||||
private static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
|
||||
private 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 boolean http_enable = true;
|
||||
public static String http_whitelist = "*";
|
||||
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
|
||||
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
|
||||
public static boolean disable_lua51_features = false;
|
||||
public static String default_computer_settings = "";
|
||||
public static boolean logPeripheralErrors = false;
|
||||
@ -185,6 +197,7 @@ public class ComputerCraft
|
||||
|
||||
public static Property http_enable;
|
||||
public static Property http_whitelist;
|
||||
public static Property http_blacklist;
|
||||
public static Property disable_lua51_features;
|
||||
public static Property default_computer_settings;
|
||||
public static Property logPeripheralErrors;
|
||||
@ -252,10 +265,28 @@ public class ComputerCraft
|
||||
Config.config.load();
|
||||
|
||||
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
|
||||
Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" for more fine grained control than this)" );
|
||||
Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" );
|
||||
|
||||
Config.http_whitelist = Config.config.get( Configuration.CATEGORY_GENERAL, "http_whitelist", http_whitelist );
|
||||
Config.http_whitelist.setComment( "A semicolon limited list of wildcards for domains that can be accessed through the \"http\" API on Computers. Set this to \"*\" to access to the entire internet. Example: \"*.pastebin.com;*.github.com;*.computercraft.info\" will restrict access to just those 3 domains." );
|
||||
{
|
||||
ConfigCategory category = Config.config.getCategory( Configuration.CATEGORY_GENERAL );
|
||||
Property currentProperty = category.get( "http_whitelist" );
|
||||
if( currentProperty != null && !currentProperty.isList() ) category.remove( "http_whitelist" );
|
||||
|
||||
Config.http_whitelist = Config.config.get( Configuration.CATEGORY_GENERAL, "http_whitelist", DEFAULT_HTTP_WHITELIST );
|
||||
|
||||
if( currentProperty != null && !currentProperty.isList() )
|
||||
{
|
||||
Config.http_whitelist.setValues( currentProperty.getString().split( ";" ) );
|
||||
}
|
||||
}
|
||||
Config.http_whitelist.setComment( "A list of wildcards for domains or IP ranges that can be accessed through the \"http\" API on Computers.\n" +
|
||||
"Set this to \"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to just subdomains of pastebin.com.\n" +
|
||||
"You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." );
|
||||
|
||||
Config.http_blacklist = Config.config.get( Configuration.CATEGORY_GENERAL, "http_blacklist", DEFAULT_HTTP_BLACKLIST );
|
||||
Config.http_blacklist.setComment( "A list of wildcards for domains or IP ranges that cannot be accessed through the \"http\" API on Computers.\n" +
|
||||
"If this is empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block access to all subdomains of github.com.\n" +
|
||||
"You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." );
|
||||
|
||||
Config.disable_lua51_features = Config.config.get( Configuration.CATEGORY_GENERAL, "disable_lua51_features", disable_lua51_features );
|
||||
Config.disable_lua51_features.setComment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. Useful for ensuring forward compatibility of your programs now." );
|
||||
@ -327,7 +358,8 @@ public class ComputerCraft
|
||||
public static void syncConfig() {
|
||||
|
||||
http_enable = Config.http_enable.getBoolean();
|
||||
http_whitelist = Config.http_whitelist.getString();
|
||||
http_whitelist = new AddressPredicate( Config.http_whitelist.getStringList() );
|
||||
http_blacklist = new AddressPredicate( Config.http_blacklist.getStringList() );
|
||||
disable_lua51_features = Config.disable_lua51_features.getBoolean();
|
||||
default_computer_settings = Config.default_computer_settings.getString();
|
||||
|
||||
|
@ -0,0 +1,168 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Used to determine whether a domain or IP address matches a series of patterns.
|
||||
*/
|
||||
public class AddressPredicate
|
||||
{
|
||||
private static class HostRange
|
||||
{
|
||||
private final byte[] min;
|
||||
private final byte[] max;
|
||||
|
||||
private HostRange( byte[] min, byte[] max )
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public boolean contains( InetAddress address )
|
||||
{
|
||||
byte[] entry = address.getAddress();
|
||||
if( entry.length != min.length ) return false;
|
||||
|
||||
for( int i = 0; i < entry.length; i++ )
|
||||
{
|
||||
int value = 0xFF & entry[ i ];
|
||||
if( value < (0xFF & min[ i ]) || value > (0xFF & max[ i ]) ) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Pattern> wildcards;
|
||||
private final List<HostRange> ranges;
|
||||
|
||||
public AddressPredicate( String... filters )
|
||||
{
|
||||
List<Pattern> wildcards = this.wildcards = new ArrayList<Pattern>();
|
||||
List<HostRange> ranges = this.ranges = new ArrayList<HostRange>();
|
||||
|
||||
for( String filter : filters )
|
||||
{
|
||||
int cidr = filter.indexOf( '/' );
|
||||
if( cidr >= 0 )
|
||||
{
|
||||
String addressStr = filter.substring( 0, cidr );
|
||||
String prefixSizeStr = filter.substring( cidr + 1 );
|
||||
|
||||
int prefixSize;
|
||||
try
|
||||
{
|
||||
prefixSize = Integer.parseInt( prefixSizeStr );
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
ComputerCraft.log.warn( "Cannot parse CIDR size from {} ({})", filter, prefixSizeStr );
|
||||
continue;
|
||||
}
|
||||
|
||||
InetAddress address;
|
||||
try
|
||||
{
|
||||
address = InetAddresses.forString( addressStr );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
ComputerCraft.log.warn( "Cannot parse IP address from {} ({})", filter, addressStr );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mask the bytes of the IP address.
|
||||
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
|
||||
int size = prefixSize;
|
||||
for( int i = 0; i < minBytes.length; i++ )
|
||||
{
|
||||
if( size <= 0 )
|
||||
{
|
||||
minBytes[ i ] &= 0;
|
||||
maxBytes[ i ] |= 0xFF;
|
||||
}
|
||||
else if( size < 8 )
|
||||
{
|
||||
minBytes[ i ] &= 0xFF << (8 - size);
|
||||
maxBytes[ i ] |= ~(0xFF << (8 - size));
|
||||
}
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
ranges.add( new HostRange( minBytes, maxBytes ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
wildcards.add( Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a host name matches a series of patterns.
|
||||
*
|
||||
* This is intended to allow early exiting, before one has to look up the IP address. You should use
|
||||
* {@link #matches(InetAddress)} instead of/in addition to this one.
|
||||
*
|
||||
* @param domain The domain to match.
|
||||
* @return Whether the patterns were matched.
|
||||
*/
|
||||
public boolean matches( String domain )
|
||||
{
|
||||
for( Pattern domainPattern : wildcards )
|
||||
{
|
||||
if( domainPattern.matcher( domain ).matches() ) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchesAddress( InetAddress address )
|
||||
{
|
||||
String addressString = address.getHostAddress();
|
||||
for( Pattern domainPattern : wildcards )
|
||||
{
|
||||
if( domainPattern.matcher( addressString ).matches() ) return true;
|
||||
}
|
||||
|
||||
for( HostRange range : ranges )
|
||||
{
|
||||
if( range.contains( address ) ) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given address matches a series of patterns
|
||||
*
|
||||
* @param address The address to check.
|
||||
* @return Whether it matches any of these patterns.
|
||||
*/
|
||||
public boolean matches( InetAddress address )
|
||||
{
|
||||
// Match the host name
|
||||
String host = address.getHostName();
|
||||
if( host != null && matches( host ) ) return true;
|
||||
|
||||
// Match the normal address
|
||||
if( matchesAddress( address ) ) return true;
|
||||
|
||||
// If we're an IPv4 address in disguise then let's check that.
|
||||
if( address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
|
||||
&& matchesAddress( InetAddresses.get6to4IPv4Address( (Inet6Address) address ) ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -7,13 +7,13 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
import dan200.computercraft.core.apis.http.HTTPCheck;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequest;
|
||||
import dan200.computercraft.core.apis.http.HTTPTask;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
@ -21,12 +21,12 @@ import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
private final IAPIEnvironment m_apiEnvironment;
|
||||
private final List<HTTPRequest> m_httpRequests;
|
||||
private final List<HTTPTask> m_httpTasks;
|
||||
|
||||
public HTTPAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_apiEnvironment = environment;
|
||||
m_httpRequests = new ArrayList<HTTPRequest>();
|
||||
m_httpTasks = new ArrayList<HTTPTask>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -46,95 +46,31 @@ public class HTTPAPI implements ILuaAPI
|
||||
public void advance( double _dt )
|
||||
{
|
||||
// Wait for all of our http requests
|
||||
synchronized( m_httpRequests )
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
Iterator<HTTPRequest> it = m_httpRequests.iterator();
|
||||
while( it.hasNext() ) {
|
||||
final HTTPRequest h = it.next();
|
||||
if( h.isComplete() ) {
|
||||
final String url = h.getURL();
|
||||
if( h.wasSuccessful() ) {
|
||||
// Queue the "http_success" event
|
||||
InputStream contents = h.getContents();
|
||||
Object result = wrapStream(
|
||||
h.isBinary() ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, h.getEncoding() ),
|
||||
h.getResponseCode(), h.getResponseHeaders()
|
||||
);
|
||||
m_apiEnvironment.queueEvent( "http_success", new Object[] { url, result } );
|
||||
} else {
|
||||
// Queue the "http_failure" event
|
||||
InputStream contents = h.getContents();
|
||||
Object result = null;
|
||||
if( contents != null ) {
|
||||
result = wrapStream(
|
||||
h.isBinary() ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, h.getEncoding() ),
|
||||
h.getResponseCode(), h.getResponseHeaders()
|
||||
);
|
||||
}
|
||||
m_apiEnvironment.queueEvent( "http_failure", new Object[]{ url, "Could not connect", result } );
|
||||
}
|
||||
Iterator<HTTPTask> it = m_httpTasks.iterator();
|
||||
while( it.hasNext() )
|
||||
{
|
||||
final HTTPTask h = it.next();
|
||||
if( h.isFinished() )
|
||||
{
|
||||
h.whenFinished( m_apiEnvironment );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ILuaObject wrapStream( final ILuaObject reader, final int responseCode, final Map<String, String> responseHeaders )
|
||||
{
|
||||
String[] oldMethods = reader.getMethodNames();
|
||||
final int methodOffset = oldMethods.length;
|
||||
|
||||
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
|
||||
newMethods[ methodOffset + 0 ] = "getResponseCode";
|
||||
newMethods[ methodOffset + 1 ] = "getResponseHeaders";
|
||||
|
||||
return new ILuaObject()
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return newMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
|
||||
{
|
||||
if( method < methodOffset )
|
||||
{
|
||||
return reader.callMethod( context, method, args );
|
||||
}
|
||||
switch( method - methodOffset )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// getResponseCode
|
||||
return new Object[] { responseCode };
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// getResponseHeaders
|
||||
return new Object[] { responseHeaders };
|
||||
}
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
synchronized( m_httpRequests )
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
for( HTTPRequest r : m_httpRequests )
|
||||
for( HTTPTask r : m_httpTasks )
|
||||
{
|
||||
r.cancel();
|
||||
}
|
||||
m_httpRequests.clear();
|
||||
m_httpTasks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,10 +124,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
// Make the request
|
||||
try
|
||||
{
|
||||
HTTPRequest request = new HTTPRequest( urlString, postString, headers, binary );
|
||||
synchronized( m_httpRequests )
|
||||
URL url = HTTPRequest.checkURL( urlString );
|
||||
HTTPRequest request = new HTTPRequest( urlString, url, postString, headers, binary );
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
m_httpRequests.add( request );
|
||||
m_httpTasks.add( HTTPTask.submit( request ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
}
|
||||
@ -209,7 +146,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
// Check URL
|
||||
try
|
||||
{
|
||||
HTTPRequest.checkURL( urlString );
|
||||
URL url = HTTPRequest.checkURL( urlString );
|
||||
HTTPCheck check = new HTTPCheck( urlString, url );
|
||||
synchronized( m_httpTasks ) {
|
||||
m_httpTasks.add( HTTPTask.submit( check ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
|
@ -1,261 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class HTTPRequest
|
||||
{
|
||||
public static URL checkURL( String urlString ) throws HTTPRequestException
|
||||
{
|
||||
URL url;
|
||||
try
|
||||
{
|
||||
url = new URL( urlString );
|
||||
}
|
||||
catch( MalformedURLException e )
|
||||
{
|
||||
throw new HTTPRequestException( "URL malformed" );
|
||||
}
|
||||
|
||||
// Validate the URL
|
||||
String protocol = url.getProtocol().toLowerCase();
|
||||
if( !protocol.equals("http") && !protocol.equals("https") )
|
||||
{
|
||||
throw new HTTPRequestException( "URL not http" );
|
||||
}
|
||||
|
||||
// Compare the URL to the whitelist
|
||||
boolean allowed = false;
|
||||
String whitelistString = ComputerCraft.http_whitelist;
|
||||
String[] allowedURLs = whitelistString.split( ";" );
|
||||
for( String allowedURL : allowedURLs )
|
||||
{
|
||||
Pattern allowedURLPattern = Pattern.compile( "^\\Q" + allowedURL.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" );
|
||||
if( allowedURLPattern.matcher( url.getHost() ).matches() )
|
||||
{
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !allowed )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public HTTPRequest( String url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
{
|
||||
// Parse the URL
|
||||
m_urlString = url;
|
||||
m_url = checkURL( m_urlString );
|
||||
m_binary = binary;
|
||||
|
||||
// Start the thread
|
||||
m_cancelled = false;
|
||||
m_complete = false;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
m_responseCode = -1;
|
||||
|
||||
Thread thread = new Thread( new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Connect to the URL
|
||||
HttpURLConnection connection = (HttpURLConnection)m_url.openConnection();
|
||||
|
||||
if( postText != null )
|
||||
{
|
||||
connection.setRequestMethod( "POST" );
|
||||
connection.setDoOutput( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.setRequestMethod( "GET" );
|
||||
}
|
||||
|
||||
// Set headers
|
||||
connection.setRequestProperty( "accept-charset", "UTF-8" );
|
||||
if( postText != null )
|
||||
{
|
||||
connection.setRequestProperty( "content-type", "application/x-www-form-urlencoded; charset=utf-8" );
|
||||
connection.setRequestProperty( "content-encoding", "UTF-8" );
|
||||
}
|
||||
if( headers != null )
|
||||
{
|
||||
for( Map.Entry<String, String> header : headers.entrySet() )
|
||||
{
|
||||
connection.setRequestProperty( header.getKey(), header.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
// Send POST text
|
||||
if( postText != null )
|
||||
{
|
||||
OutputStream os = connection.getOutputStream();
|
||||
OutputStreamWriter osw;
|
||||
try
|
||||
{
|
||||
osw = new OutputStreamWriter( os, "UTF-8" );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
osw = new OutputStreamWriter( os );
|
||||
}
|
||||
BufferedWriter writer = new BufferedWriter( osw );
|
||||
writer.write( postText, 0, postText.length() );
|
||||
writer.close();
|
||||
}
|
||||
|
||||
// Read response
|
||||
InputStream is;
|
||||
int code = connection.getResponseCode();
|
||||
boolean responseSuccess;
|
||||
if (code >= 200 && code < 400) {
|
||||
is = connection.getInputStream();
|
||||
responseSuccess = true;
|
||||
} else {
|
||||
is = connection.getErrorStream();
|
||||
responseSuccess = false;
|
||||
}
|
||||
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
is.close();
|
||||
|
||||
synchronized( m_lock )
|
||||
{
|
||||
if( m_cancelled )
|
||||
{
|
||||
// We cancelled
|
||||
m_complete = true;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We completed
|
||||
m_complete = true;
|
||||
m_success = responseSuccess;
|
||||
m_result = result;
|
||||
m_responseCode = connection.getResponseCode();
|
||||
m_encoding = connection.getContentEncoding();
|
||||
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers = m_responseHeaders = new HashMap<String, String>();
|
||||
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
|
||||
headers.put(header.getKey(), joiner.join( header.getValue() ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connection.disconnect(); // disconnect
|
||||
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
synchronized( m_lock )
|
||||
{
|
||||
// There was an error
|
||||
m_complete = true;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return m_urlString;
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
m_cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isComplete()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
return m_complete;
|
||||
}
|
||||
}
|
||||
|
||||
public int getResponseCode() {
|
||||
synchronized(m_lock) {
|
||||
return m_responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getResponseHeaders() {
|
||||
synchronized (m_lock) {
|
||||
return m_responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasSuccessful()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
return m_success;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBinary()
|
||||
{
|
||||
return m_binary;
|
||||
}
|
||||
|
||||
public InputStream getContents()
|
||||
{
|
||||
byte[] result;
|
||||
synchronized(m_lock) {
|
||||
result = m_result;
|
||||
}
|
||||
|
||||
if( result != null ) {
|
||||
return new ByteArrayInputStream( result );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return m_encoding;
|
||||
}
|
||||
|
||||
private final Object m_lock = new Object();
|
||||
private final URL m_url;
|
||||
private final String m_urlString;
|
||||
|
||||
private boolean m_complete;
|
||||
private boolean m_cancelled;
|
||||
private boolean m_success;
|
||||
private String m_encoding;
|
||||
private byte[] m_result;
|
||||
private boolean m_binary;
|
||||
private int m_responseCode;
|
||||
private Map<String, String> m_responseHeaders;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import dan200.computercraft.core.apis.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class HTTPCheck implements HTTPTask.IHTTPTask
|
||||
{
|
||||
private final String urlString;
|
||||
private final URL url;
|
||||
private String error;
|
||||
|
||||
public HTTPCheck( String urlString, URL url )
|
||||
{
|
||||
this.urlString = urlString;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
HTTPRequest.checkHost( url );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
error = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
if( error == null )
|
||||
{
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, true } );
|
||||
}
|
||||
else
|
||||
{
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, false, error } );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
{
|
||||
public static URL checkURL( String urlString ) throws HTTPRequestException
|
||||
{
|
||||
URL url;
|
||||
try
|
||||
{
|
||||
url = new URL( urlString );
|
||||
}
|
||||
catch( MalformedURLException e )
|
||||
{
|
||||
throw new HTTPRequestException( "URL malformed" );
|
||||
}
|
||||
|
||||
// Validate the URL
|
||||
String protocol = url.getProtocol().toLowerCase();
|
||||
if( !protocol.equals( "http" ) && !protocol.equals( "https" ) )
|
||||
{
|
||||
throw new HTTPRequestException( "URL not http" );
|
||||
}
|
||||
|
||||
// Compare the URL to the whitelist
|
||||
if( !ComputerCraft.http_whitelist.matches( url.getHost() ) || ComputerCraft.http_blacklist.matches( url.getHost() ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public static InetAddress checkHost( URL url ) throws HTTPRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
InetAddress resolved = InetAddress.getByName( url.getHost() );
|
||||
if( !ComputerCraft.http_whitelist.matches( resolved ) || ComputerCraft.http_blacklist.matches( resolved ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
catch( UnknownHostException e )
|
||||
{
|
||||
throw new HTTPRequestException( "Unknown host" );
|
||||
}
|
||||
}
|
||||
|
||||
private final URL m_url;
|
||||
private final String m_urlString;
|
||||
private final String m_postText;
|
||||
private final Map<String, String> m_headers;
|
||||
|
||||
private boolean m_success = false;
|
||||
private String m_encoding;
|
||||
private byte[] m_result;
|
||||
private boolean m_binary;
|
||||
private int m_responseCode = -1;
|
||||
private Map<String, String> m_responseHeaders;
|
||||
private String m_errorMessage;
|
||||
|
||||
public HTTPRequest( String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
{
|
||||
// Parse the URL
|
||||
m_urlString = urlString;
|
||||
m_url = url;
|
||||
m_binary = binary;
|
||||
m_postText = postText;
|
||||
m_headers = headers;
|
||||
}
|
||||
|
||||
public InputStream getContents()
|
||||
{
|
||||
byte[] result = m_result;
|
||||
if( result != null )
|
||||
{
|
||||
return new ByteArrayInputStream( result );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// First verify the address is allowed.
|
||||
try
|
||||
{
|
||||
checkHost( m_url );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
m_success = false;
|
||||
m_errorMessage = e.getMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Connect to the URL
|
||||
HttpURLConnection connection = (HttpURLConnection) m_url.openConnection();
|
||||
|
||||
if( m_postText != null )
|
||||
{
|
||||
connection.setRequestMethod( "POST" );
|
||||
connection.setDoOutput( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.setRequestMethod( "GET" );
|
||||
}
|
||||
|
||||
// Set headers
|
||||
connection.setRequestProperty( "accept-charset", "UTF-8" );
|
||||
if( m_postText != null )
|
||||
{
|
||||
connection.setRequestProperty( "content-type", "application/x-www-form-urlencoded; charset=utf-8" );
|
||||
connection.setRequestProperty( "content-encoding", "UTF-8" );
|
||||
}
|
||||
if( m_postText != null )
|
||||
{
|
||||
for( Map.Entry<String, String> header : m_headers.entrySet() )
|
||||
{
|
||||
connection.setRequestProperty( header.getKey(), header.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
// Send POST text
|
||||
if( m_postText != null )
|
||||
{
|
||||
OutputStream os = connection.getOutputStream();
|
||||
OutputStreamWriter osw;
|
||||
try
|
||||
{
|
||||
osw = new OutputStreamWriter( os, "UTF-8" );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
osw = new OutputStreamWriter( os );
|
||||
}
|
||||
BufferedWriter writer = new BufferedWriter( osw );
|
||||
writer.write( m_postText, 0, m_postText.length() );
|
||||
writer.close();
|
||||
}
|
||||
|
||||
// Read response
|
||||
InputStream is;
|
||||
int code = connection.getResponseCode();
|
||||
boolean responseSuccess;
|
||||
if( code >= 200 && code < 400 )
|
||||
{
|
||||
is = connection.getInputStream();
|
||||
responseSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
is = connection.getErrorStream();
|
||||
responseSuccess = false;
|
||||
}
|
||||
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
is.close();
|
||||
|
||||
// We completed
|
||||
m_success = responseSuccess;
|
||||
m_result = result;
|
||||
m_responseCode = connection.getResponseCode();
|
||||
m_encoding = connection.getContentEncoding();
|
||||
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers = m_responseHeaders = new HashMap<String, String>();
|
||||
for( Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet() )
|
||||
{
|
||||
headers.put( header.getKey(), joiner.join( header.getValue() ) );
|
||||
}
|
||||
|
||||
connection.disconnect(); // disconnect
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
// There was an error
|
||||
m_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
final String url = m_urlString;
|
||||
if( m_success )
|
||||
{
|
||||
// Queue the "http_success" event
|
||||
InputStream contents = getContents();
|
||||
Object result = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
|
||||
m_responseCode, m_responseHeaders
|
||||
);
|
||||
environment.queueEvent( "http_success", new Object[] { url, result } );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue the "http_failure" event
|
||||
String error = "Could not connect";
|
||||
if( m_errorMessage != null ) error = m_errorMessage;
|
||||
|
||||
InputStream contents = getContents();
|
||||
Object result = null;
|
||||
if( contents != null )
|
||||
{
|
||||
result = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
|
||||
m_responseCode, m_responseHeaders
|
||||
);
|
||||
}
|
||||
environment.queueEvent( "http_failure", new Object[] { url, error, result } );
|
||||
}
|
||||
}
|
||||
|
||||
private static ILuaObject wrapStream( final ILuaObject reader, final int responseCode, final Map<String, String> responseHeaders )
|
||||
{
|
||||
String[] oldMethods = reader.getMethodNames();
|
||||
final int methodOffset = oldMethods.length;
|
||||
|
||||
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
|
||||
newMethods[ methodOffset + 0 ] = "getResponseCode";
|
||||
newMethods[ methodOffset + 1 ] = "getResponseHeaders";
|
||||
|
||||
return new ILuaObject()
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return newMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
|
||||
{
|
||||
if( method < methodOffset )
|
||||
{
|
||||
return reader.callMethod( context, method, args );
|
||||
}
|
||||
switch( method - methodOffset )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// getResponseCode
|
||||
return new Object[] { responseCode };
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// getResponseHeaders
|
||||
return new Object[] { responseHeaders };
|
||||
}
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* A task which executes asynchronously on a new thread.
|
||||
*
|
||||
* This functions very similarly to a {@link Future}, but with an additional
|
||||
* method which is called on the main thread when the task is completed.
|
||||
*/
|
||||
public class HTTPTask
|
||||
{
|
||||
public interface IHTTPTask extends Runnable
|
||||
{
|
||||
void whenFinished( IAPIEnvironment environment );
|
||||
}
|
||||
|
||||
private static final ExecutorService httpThreads = new ThreadPoolExecutor(
|
||||
4, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon( true )
|
||||
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
|
||||
.setNameFormat( "ComputerCraft-HTTP-%d" )
|
||||
.build()
|
||||
);
|
||||
|
||||
private final Future<?> future;
|
||||
private final IHTTPTask task;
|
||||
|
||||
private HTTPTask( Future<?> future, IHTTPTask task )
|
||||
{
|
||||
this.future = future;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public static HTTPTask submit( IHTTPTask task )
|
||||
{
|
||||
Future<?> future = httpThreads.submit( task );
|
||||
return new HTTPTask( future, task );
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
future.cancel( false );
|
||||
}
|
||||
|
||||
public boolean isFinished()
|
||||
{
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
task.whenFinished( environment );
|
||||
}
|
||||
}
|
@ -24,13 +24,16 @@ import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntitySign;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.EnumActionResult;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.Event;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -215,7 +218,6 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
}
|
||||
|
||||
// Load up the turtle's inventory
|
||||
Item item = stack.getItem();
|
||||
ItemStack stackCopy = stack.copy();
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
|
||||
@ -237,21 +239,26 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
|
||||
// Place on the entity
|
||||
boolean placed = false;
|
||||
if( hitEntity.applyPlayerInteraction( turtlePlayer, hitPos, stackCopy, EnumHand.MAIN_HAND ) == EnumActionResult.SUCCESS )
|
||||
if( !ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, stack, EnumHand.MAIN_HAND ) &&
|
||||
hitEntity.applyPlayerInteraction( turtlePlayer, hitPos, stackCopy, EnumHand.MAIN_HAND ) == EnumActionResult.SUCCESS )
|
||||
{
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
}
|
||||
else if( hitEntity.processInitialInteract( turtlePlayer, stackCopy, EnumHand.MAIN_HAND ) )
|
||||
else if( !ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, stack, EnumHand.MAIN_HAND ) )
|
||||
{
|
||||
placed = true;
|
||||
}
|
||||
else if( hitEntity instanceof EntityLivingBase )
|
||||
{
|
||||
placed = item.itemInteractionForEntity( stackCopy, turtlePlayer, (EntityLivingBase)hitEntity, EnumHand.MAIN_HAND );
|
||||
if( placed )
|
||||
// See EntityPlayer.interact
|
||||
if( hitEntity.processInitialInteract( turtlePlayer, stackCopy, EnumHand.MAIN_HAND ) )
|
||||
{
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
placed = true;
|
||||
}
|
||||
else if( hitEntity instanceof EntityLivingBase )
|
||||
{
|
||||
placed = stackCopy.interactWithEntity( turtlePlayer, (EntityLivingBase) hitEntity, EnumHand.MAIN_HAND );
|
||||
if( placed )
|
||||
{
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,19 +359,29 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
|
||||
// Do the deploying (put everything in the players inventory)
|
||||
boolean placed = false;
|
||||
if( item.onItemUseFirst( stackCopy, turtlePlayer, turtle.getWorld(), position, side, hitX, hitY, hitZ, EnumHand.MAIN_HAND ) == EnumActionResult.SUCCESS )
|
||||
|
||||
|
||||
// See PlayerInteractionManager.processRightClickBlock
|
||||
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, EnumHand.MAIN_HAND, stackCopy, position, side, new Vec3d( hitX, hitY, hitZ ) );
|
||||
if( !event.isCanceled() )
|
||||
{
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
if( item.onItemUseFirst( stackCopy, turtlePlayer, turtle.getWorld(), position, side, hitX, hitY, hitZ, EnumHand.MAIN_HAND ) == EnumActionResult.SUCCESS )
|
||||
{
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
}
|
||||
else if( event.getUseItem() != Event.Result.DENY &&
|
||||
stackCopy.onItemUse( turtlePlayer, turtle.getWorld(), position, EnumHand.MAIN_HAND, side, hitX, hitY, hitZ ) == EnumActionResult.SUCCESS )
|
||||
{
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
}
|
||||
}
|
||||
else if( item.onItemUse( stackCopy, turtlePlayer, turtle.getWorld(), position, EnumHand.MAIN_HAND, side, hitX, hitY, hitZ ) == EnumActionResult.SUCCESS )
|
||||
|
||||
if( !placed && (item instanceof ItemBucket || item instanceof ItemBoat || item instanceof ItemLilyPad || item instanceof ItemGlassBottle)
|
||||
&& ForgeHooks.onItemRightClick( turtlePlayer, EnumHand.MAIN_HAND, stackCopy ) )
|
||||
{
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
}
|
||||
else if( item instanceof ItemBucket || item instanceof ItemBoat || item instanceof ItemLilyPad || item instanceof ItemGlassBottle )
|
||||
{
|
||||
ActionResult<ItemStack> result = item.onItemRightClick( stackCopy, turtle.getWorld(), turtlePlayer, EnumHand.MAIN_HAND );
|
||||
ActionResult<ItemStack> result = stackCopy.useItemRightClick( turtle.getWorld(), turtlePlayer, EnumHand.MAIN_HAND );
|
||||
if( result.getType() == EnumActionResult.SUCCESS && !ItemStack.areItemStacksEqual( stack, result.getResult() ) )
|
||||
{
|
||||
placed = true;
|
||||
|
@ -22,14 +22,22 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
import net.minecraft.entity.item.EntityArmorStand;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.*;
|
||||
import net.minecraft.util.math.*;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@ -189,9 +197,10 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
}
|
||||
} );
|
||||
|
||||
// Place on the entity
|
||||
boolean placed = false;
|
||||
if( hitEntity.canBeAttackedWithItem() && !hitEntity.hitByEntity( turtlePlayer ) )
|
||||
// Attack the entity
|
||||
boolean attacked = false;
|
||||
if( hitEntity.canBeAttackedWithItem() && !hitEntity.hitByEntity( turtlePlayer )
|
||||
&& !MinecraftForge.EVENT_BUS.post( new AttackEntityEvent( turtlePlayer, hitEntity ) ) )
|
||||
{
|
||||
float damage = (float)turtlePlayer.getEntityAttribute( SharedMonsterAttributes.ATTACK_DAMAGE ).getAttributeValue();
|
||||
damage *= getDamageMultiplier();
|
||||
@ -206,13 +215,13 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
{
|
||||
hitEntity.attackEntityFrom( source, damage );
|
||||
}
|
||||
placed = true;
|
||||
attacked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( hitEntity.attackEntityFrom( source, damage ) )
|
||||
{
|
||||
placed = true;
|
||||
attacked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -222,7 +231,7 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
ComputerCraft.clearEntityDropConsumer( hitEntity );
|
||||
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
if( placed )
|
||||
if( attacked )
|
||||
{
|
||||
turtlePlayer.unloadInventory( turtle );
|
||||
return TurtleCommandResult.success();
|
||||
@ -243,10 +252,16 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
!world.isAirBlock( newPosition ) &&
|
||||
!WorldUtil.isLiquidBlock( world, newPosition ) )
|
||||
{
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction );
|
||||
if( ComputerCraft.turtlesObeyBlockProtection )
|
||||
{
|
||||
// Check spawn protection
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction );
|
||||
|
||||
if( MinecraftForge.EVENT_BUS.post( new BlockEvent.BreakEvent( world, newPosition, world.getBlockState( newPosition ), turtlePlayer ) ) )
|
||||
{
|
||||
return TurtleCommandResult.failure( "Cannot break protected block" );
|
||||
}
|
||||
|
||||
if( !ComputerCraft.isBlockEditable( world, newPosition, turtlePlayer ) )
|
||||
{
|
||||
return TurtleCommandResult.failure( "Cannot break protected block" );
|
||||
@ -262,7 +277,7 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
// Consume the items the block drops
|
||||
if( canHarvestBlock( world, newPosition ) )
|
||||
{
|
||||
List<ItemStack> items = getBlockDropped( world, newPosition );
|
||||
List<ItemStack> items = getBlockDropped( world, newPosition, turtlePlayer );
|
||||
if( items != null && items.size() > 0 )
|
||||
{
|
||||
for( ItemStack stack : items )
|
||||
@ -279,7 +294,7 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
|
||||
// Destroy the block
|
||||
IBlockState previousState = world.getBlockState( newPosition );
|
||||
world.playEvent(2001, newPosition, Block.getStateId(previousState));
|
||||
world.playEvent(2001, newPosition, Block.getStateId(previousState));
|
||||
world.setBlockToAir( newPosition );
|
||||
|
||||
// Remember the previous block
|
||||
@ -295,9 +310,20 @@ public class TurtleTool implements ITurtleUpgrade
|
||||
return TurtleCommandResult.failure( "Nothing to dig here" );
|
||||
}
|
||||
|
||||
private java.util.List<ItemStack> getBlockDropped( World world, BlockPos pos )
|
||||
private List<ItemStack> getBlockDropped( World world, BlockPos pos, EntityPlayer player )
|
||||
{
|
||||
Block block = world.getBlockState( pos ).getBlock();
|
||||
return block.getDrops( world, pos, world.getBlockState( pos ), 0 );
|
||||
IBlockState state = world.getBlockState( pos );
|
||||
Block block = state.getBlock();
|
||||
List<ItemStack> drops = block.getDrops( world, pos, world.getBlockState( pos ), 0 );
|
||||
double chance = ForgeEventFactory.fireBlockHarvesting( drops, world, pos, state, 0, 1, false, player );
|
||||
|
||||
for( int i = drops.size() - 1; i >= 0; i-- )
|
||||
{
|
||||
if( world.rand.nextFloat() > chance )
|
||||
{
|
||||
drops.remove( i );
|
||||
}
|
||||
}
|
||||
return drops;
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +163,13 @@ public class InventoryUtil
|
||||
}
|
||||
|
||||
// Inspect the slots in order and try to find empty or stackable slots
|
||||
ItemStack remainder = stack;
|
||||
ItemStack remainder = stack.copy();
|
||||
for( int slot : slots )
|
||||
{
|
||||
if( remainder == null ) break;
|
||||
remainder = inventory.insertItem( slot, remainder, false );
|
||||
}
|
||||
return remainder;
|
||||
return areItemsEqual( stack, remainder ) ? stack : remainder;
|
||||
}
|
||||
|
||||
private static ItemStack takeItems( int count, IItemHandler inventory, int[] slots )
|
||||
|
@ -44,6 +44,7 @@ gui.computercraft:wired_modem.peripheral_disconnected=Peripheral "%s" disconnect
|
||||
|
||||
gui.computercraft:config.http_enable=Enable HTTP API
|
||||
gui.computercraft:config.http_whitelist=HTTP whitelist
|
||||
gui.computercraft:config.http_blacklist=HTTP blacklist
|
||||
gui.computercraft:config.disable_lua51_features=Disable Lua 5.1 features
|
||||
gui.computercraft:config.default_computer_settings=Default Computer settings
|
||||
gui.computercraft:config.log_peripheral_errors=Log peripheral errors
|
||||
|
@ -729,8 +729,8 @@ if http then
|
||||
if type( _url ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
|
||||
end
|
||||
if _post ~= nil and type( _post ) ~= "table" then
|
||||
error( "bad argument #2 (expected table, got " .. type( _post ) .. ")", 2 )
|
||||
if _post ~= nil and type( _post ) ~= "string" then
|
||||
error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 2 )
|
||||
end
|
||||
if _headers ~= nil and type( _headers ) ~= "table" then
|
||||
error( "bad argument #3 (expected table, got " .. type( _headers ) .. ")", 2 )
|
||||
@ -741,6 +741,18 @@ if http then
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local nativeCheckURL = http.checkURL
|
||||
http.checkURLAsync = nativeCheckURL
|
||||
http.checkURL = function( _url )
|
||||
local ok, err = nativeCheckURL( _url )
|
||||
if not ok then return ok, err end
|
||||
|
||||
while true do
|
||||
local event, url, ok, err = os.pullEvent( "http_check" )
|
||||
if url == _url then return ok, err end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Install the lua part of the FS api
|
||||
@ -828,7 +840,7 @@ for n,sFile in ipairs( tApis ) do
|
||||
end
|
||||
end
|
||||
|
||||
if turtle then
|
||||
if turtle and fs.isDir( "rom/apis/turtle" ) then
|
||||
-- Load turtle APIs
|
||||
local tApis = fs.list( "rom/apis/turtle" )
|
||||
for n,sFile in ipairs( tApis ) do
|
||||
|
@ -19,32 +19,51 @@ black = 32768
|
||||
function combine( ... )
|
||||
local r = 0
|
||||
for n,c in ipairs( { ... } ) do
|
||||
if type( c ) ~= "number" then
|
||||
error( "bad argument #"..n.." (expected number, got " .. type( c ) .. ")", 2 )
|
||||
end
|
||||
r = bit32.bor(r,c)
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function subtract( colors, ... )
|
||||
if type( colors ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( colors ) .. ")", 2 )
|
||||
end
|
||||
local r = colors
|
||||
for n,c in ipairs( { ... } ) do
|
||||
if type( c ) ~= "number" then
|
||||
error( "bad argument #"..tostring( n+1 ).." (expected number, got " .. type( c ) .. ")", 2 )
|
||||
end
|
||||
r = bit32.band(r, bit32.bnot(c))
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function test( colors, color )
|
||||
if type( colors ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( colors ) .. ")", 2 )
|
||||
end
|
||||
if type( color ) ~= "number" then
|
||||
error( "bad argument #2 (expected number, got " .. type( color ) .. ")", 2 )
|
||||
end
|
||||
return ((bit32.band(colors, color)) == color)
|
||||
end
|
||||
|
||||
function rgb8( r, g, b )
|
||||
if type(r) == "number" and g == nil and b == nil then
|
||||
if type( r ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( r ) .. ")", 2 )
|
||||
elseif type(r) == "number" and g == nil and b == nil then
|
||||
return bit32.band( bit32.rshift( r, 16 ), 0xFF ) / 255, bit32.band( bit32.rshift( r, 8 ), 0xFF ) / 255, bit32.band( r, 0xFF ) / 255
|
||||
elseif type(r) == "number" and type(g) == "number" and type(b) == "number" then
|
||||
return
|
||||
bit32.lshift( bit32.band(r * 255, 0xFF), 16 ) +
|
||||
bit32.lshift( bit32.band(g * 255, 0xFF), 8 ) +
|
||||
bit32.band(b * 255, 0xFF)
|
||||
else
|
||||
error( "Expected 1 or 3 numbers", 2 )
|
||||
elseif type( g ) ~= "number" then
|
||||
error( "bad argument #2 (expected number, got " .. type( g ) .. ")", 2 )
|
||||
elseif type( b ) ~= "number" then
|
||||
error( "bad argument #3 (expected number, got " .. type( b ) .. ")", 2 )
|
||||
end
|
||||
end
|
||||
|
@ -7,14 +7,14 @@ local tReceivedMessageTimeouts = {}
|
||||
local tHostnames = {}
|
||||
|
||||
function open( sModem )
|
||||
if type( sModem ) ~= "string" then
|
||||
error( "expected string", 2 )
|
||||
end
|
||||
if peripheral.getType( sModem ) ~= "modem" then
|
||||
error( "No such modem: "..sModem, 2 )
|
||||
end
|
||||
peripheral.call( sModem, "open", os.getComputerID() )
|
||||
peripheral.call( sModem, "open", CHANNEL_BROADCAST )
|
||||
if type( sModem ) ~= "string" then
|
||||
error( "expected string", 2 )
|
||||
end
|
||||
if peripheral.getType( sModem ) ~= "modem" then
|
||||
error( "No such modem: "..sModem, 2 )
|
||||
end
|
||||
peripheral.call( sModem, "open", os.getComputerID() )
|
||||
peripheral.call( sModem, "open", CHANNEL_BROADCAST )
|
||||
end
|
||||
|
||||
function close( sModem )
|
||||
@ -55,7 +55,7 @@ function isOpen( sModem )
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
function send( nRecipient, message, sProtocol )
|
||||
@ -93,7 +93,7 @@ function send( nRecipient, message, sProtocol )
|
||||
end
|
||||
|
||||
function broadcast( message, sProtocol )
|
||||
send( CHANNEL_BROADCAST, message, sProtocol )
|
||||
send( CHANNEL_BROADCAST, message, sProtocol )
|
||||
end
|
||||
|
||||
function receive( sProtocolFilter, nTimeout )
|
||||
@ -103,31 +103,31 @@ function receive( sProtocolFilter, nTimeout )
|
||||
end
|
||||
|
||||
-- Start the timer
|
||||
local timer = nil
|
||||
local sFilter = nil
|
||||
if nTimeout then
|
||||
timer = os.startTimer( nTimeout )
|
||||
sFilter = nil
|
||||
else
|
||||
sFilter = "rednet_message"
|
||||
end
|
||||
local timer = nil
|
||||
local sFilter = nil
|
||||
if nTimeout then
|
||||
timer = os.startTimer( nTimeout )
|
||||
sFilter = nil
|
||||
else
|
||||
sFilter = "rednet_message"
|
||||
end
|
||||
|
||||
-- Wait for events
|
||||
while true do
|
||||
local sEvent, p1, p2, p3 = os.pullEvent( sFilter )
|
||||
if sEvent == "rednet_message" then
|
||||
-- Return the first matching rednet_message
|
||||
local nSenderID, message, sProtocol = p1, p2, p3
|
||||
if sProtocolFilter == nil or sProtocol == sProtocolFilter then
|
||||
return nSenderID, message, sProtocol
|
||||
end
|
||||
elseif sEvent == "timer" then
|
||||
-- Return nil if we timeout
|
||||
if p1 == timer then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Wait for events
|
||||
while true do
|
||||
local sEvent, p1, p2, p3 = os.pullEvent( sFilter )
|
||||
if sEvent == "rednet_message" then
|
||||
-- Return the first matching rednet_message
|
||||
local nSenderID, message, sProtocol = p1, p2, p3
|
||||
if sProtocolFilter == nil or sProtocol == sProtocolFilter then
|
||||
return nSenderID, message, sProtocol
|
||||
end
|
||||
elseif sEvent == "timer" then
|
||||
-- Return nil if we timeout
|
||||
if p1 == timer then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function host( sProtocol, sHostname )
|
||||
@ -219,41 +219,41 @@ end
|
||||
|
||||
local bRunning = false
|
||||
function run()
|
||||
if bRunning then
|
||||
error( "rednet is already running", 2 )
|
||||
end
|
||||
bRunning = true
|
||||
|
||||
while bRunning do
|
||||
local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
|
||||
if sEvent == "modem_message" then
|
||||
-- Got a modem message, process it and add it to the rednet event queue
|
||||
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
|
||||
if isOpen( sModem ) and ( nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST ) then
|
||||
if type( tMessage ) == "table" and tMessage.nMessageID then
|
||||
if not tReceivedMessages[ tMessage.nMessageID ] then
|
||||
tReceivedMessages[ tMessage.nMessageID ] = true
|
||||
tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
|
||||
os.queueEvent( "rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol )
|
||||
end
|
||||
end
|
||||
end
|
||||
if bRunning then
|
||||
error( "rednet is already running", 2 )
|
||||
end
|
||||
bRunning = true
|
||||
|
||||
while bRunning do
|
||||
local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
|
||||
if sEvent == "modem_message" then
|
||||
-- Got a modem message, process it and add it to the rednet event queue
|
||||
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
|
||||
if isOpen( sModem ) and ( nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST ) then
|
||||
if type( tMessage ) == "table" and tMessage.nMessageID then
|
||||
if not tReceivedMessages[ tMessage.nMessageID ] then
|
||||
tReceivedMessages[ tMessage.nMessageID ] = true
|
||||
tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = tMessage.nMessageID
|
||||
os.queueEvent( "rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif sEvent == "rednet_message" then
|
||||
-- Got a rednet message (queued from above), respond to dns lookup
|
||||
local nSenderID, tMessage, sProtocol = p1, p2, p3
|
||||
if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then
|
||||
local sHostname = tHostnames[ tMessage.sProtocol ]
|
||||
if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
|
||||
rednet.send( nSenderID, {
|
||||
sType = "lookup response",
|
||||
sHostname = sHostname,
|
||||
sProtocol = tMessage.sProtocol,
|
||||
}, "dns" )
|
||||
end
|
||||
end
|
||||
elseif sEvent == "rednet_message" then
|
||||
-- Got a rednet message (queued from above), respond to dns lookup
|
||||
local nSenderID, tMessage, sProtocol = p1, p2, p3
|
||||
if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then
|
||||
local sHostname = tHostnames[ tMessage.sProtocol ]
|
||||
if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
|
||||
rednet.send( nSenderID, {
|
||||
sType = "lookup response",
|
||||
sHostname = sHostname,
|
||||
sProtocol = tMessage.sProtocol,
|
||||
}, "dns" )
|
||||
end
|
||||
end
|
||||
|
||||
elseif sEvent == "timer" then
|
||||
elseif sEvent == "timer" then
|
||||
-- Got a timer event, use it to clear the event queue
|
||||
local nTimer = p1
|
||||
local nMessage = tReceivedMessageTimeouts[ nTimer ]
|
||||
@ -261,6 +261,6 @@ function run()
|
||||
tReceivedMessageTimeouts[ nTimer ] = nil
|
||||
tReceivedMessages[ nMessage ] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -56,6 +56,7 @@ function getNames()
|
||||
for k,v in pairs( tSettings ) do
|
||||
result[ #result + 1 ] = k
|
||||
end
|
||||
table.sort(result)
|
||||
return result
|
||||
end
|
||||
|
||||
|
@ -7,7 +7,7 @@ function slowWrite( sText, nRate )
|
||||
local nSleep = 1 / nRate
|
||||
|
||||
sText = tostring( sText )
|
||||
local x,y = term.getCursorPos(x,y)
|
||||
local x,y = term.getCursorPos()
|
||||
local len = string.len( sText )
|
||||
|
||||
for n=1,len do
|
||||
@ -375,6 +375,8 @@ function complete( sSearchText, tSearchTable )
|
||||
if type( tSearchTable ) ~= "table" then
|
||||
error( "bad argument #2 (expected table, got " .. type( tSearchTable ) .. ")", 2 )
|
||||
end
|
||||
|
||||
if g_tLuaKeywords[sSearchText] then return tEmpty end
|
||||
local nStart = 1
|
||||
local nDot = string.find( sSearchText, ".", nStart, true )
|
||||
local tTable = tSearchTable or _ENV
|
||||
@ -389,8 +391,19 @@ function complete( sSearchText, tSearchTable )
|
||||
return tEmpty
|
||||
end
|
||||
end
|
||||
|
||||
local sPart = string.sub( sSearchText, nStart, nDot )
|
||||
local nColon = string.find( sSearchText, ":", nStart, true )
|
||||
if nColon then
|
||||
local sPart = string.sub( sSearchText, nStart, nColon - 1 )
|
||||
local value = tTable[ sPart ]
|
||||
if type( value ) == "table" then
|
||||
tTable = value
|
||||
nStart = nColon + 1
|
||||
else
|
||||
return tEmpty
|
||||
end
|
||||
end
|
||||
|
||||
local sPart = string.sub( sSearchText, nStart )
|
||||
local nPartLength = string.len( sPart )
|
||||
|
||||
local tResults = {}
|
||||
|
@ -1,4 +1,5 @@
|
||||
equip is a program for Turtles. equip will equip an item from the Turtle's inventory for use as a tool of peripheral.
|
||||
equip is a program for Turtles and Pocket Computer. equip will equip an item from the Turtle's inventory for use as a tool of peripheral. On a Pocket Computer you don't need to write a side.
|
||||
|
||||
ex:
|
||||
"equip 5 left" will equip the item from slot 5 of the turtle onto the left side of the turtle
|
||||
"equip" on a Pocket Computer will equip the first item from your inventory.
|
||||
|
@ -1,5 +1,6 @@
|
||||
Functions in the HTTP API:
|
||||
http.checkURL( url )
|
||||
http.checkURLAsync( url )
|
||||
http.request( url, [postData], [headers] )
|
||||
http.get( url, [headers] )
|
||||
http.post( url, postData, [headers] )
|
||||
|
@ -1,4 +1,5 @@
|
||||
unequip is a program for Turtles. unequip will remove tools of peripherals from the specified side of the turtle.
|
||||
unequip is a program for Turtles and Pocket Computers. unequip will remove tools of peripherals from the specified side of the turtle. On a Pocket Computer you don't need to write a side.
|
||||
|
||||
ex:
|
||||
"unequip left" will remove the item on the left side of the turtle
|
||||
"unequip" on a Pocket Computer will remove the item from the Pocket Computer
|
||||
|
@ -171,6 +171,9 @@ function multishell.getFocus()
|
||||
end
|
||||
|
||||
function multishell.setFocus( n )
|
||||
if type( n ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( n ) .. ")", 2 )
|
||||
end
|
||||
if n >= 1 and n <= #tProcesses then
|
||||
selectProcess( n )
|
||||
redrawMenu()
|
||||
@ -180,6 +183,9 @@ function multishell.setFocus( n )
|
||||
end
|
||||
|
||||
function multishell.getTitle( n )
|
||||
if type( n ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( n ) .. ")", 2 )
|
||||
end
|
||||
if n >= 1 and n <= #tProcesses then
|
||||
return tProcesses[n].sTitle
|
||||
end
|
||||
@ -187,6 +193,12 @@ function multishell.getTitle( n )
|
||||
end
|
||||
|
||||
function multishell.setTitle( n, sTitle )
|
||||
if type( n ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( n ) .. ")", 2 )
|
||||
end
|
||||
if type( sTitle ) ~= "string" then
|
||||
error( "bad argument #2 (expected string, got " .. type( sTitle ) .. ")", 2 )
|
||||
end
|
||||
if n >= 1 and n <= #tProcesses then
|
||||
setProcessTitle( n, sTitle )
|
||||
redrawMenu()
|
||||
@ -198,6 +210,12 @@ function multishell.getCurrent()
|
||||
end
|
||||
|
||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
||||
if type( tProgramArgs ) ~= "table" then
|
||||
error( "bad argument #1 (expected table, got " .. type( tProgramEnv ) .. ")", 2 )
|
||||
end
|
||||
if type( sProgramPath ) ~= "string" then
|
||||
error( "bad argument #2 (expected string, got " .. type( sProgramPath ) .. ")", 2 )
|
||||
end
|
||||
local previousTerm = term.current()
|
||||
setMenuVisible( (#tProcesses + 1) >= 2 )
|
||||
local nResult = launchProcess( tProgramEnv, sProgramPath, ... )
|
||||
|
@ -175,7 +175,7 @@ local nCompletion
|
||||
local tCompleteEnv = _ENV
|
||||
local function complete( sLine )
|
||||
if settings.get( "edit.autocomplete" ) then
|
||||
local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.]+$" )
|
||||
local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.:]+$" )
|
||||
if nStartPos then
|
||||
sLine = string.sub( sLine, nStartPos )
|
||||
end
|
||||
@ -709,7 +709,13 @@ while bRunning do
|
||||
end
|
||||
|
||||
elseif sEvent == "paste" then
|
||||
if not bMenu and not bReadOnly then
|
||||
if not bReadOnly then
|
||||
-- Close menu if open
|
||||
if bMenu then
|
||||
bMenu = false
|
||||
term.setCursorBlink( true )
|
||||
redrawMenu()
|
||||
end
|
||||
-- Input text
|
||||
local sLine = tLines[y]
|
||||
tLines[y] = string.sub(sLine,1,x-1) .. param .. string.sub(sLine,x)
|
||||
|
@ -57,10 +57,6 @@ elseif sCommand == "host" then
|
||||
x,y,z = gps.locate( 2, true )
|
||||
if x == nil then
|
||||
print( "Run \"gps host <x> <y> <z>\" to set position manually" )
|
||||
if bCloseChannel then
|
||||
print( "Closing GPS channel" )
|
||||
modem.close( gps.CHANNEL_GPS )
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
@ -34,7 +34,7 @@ while bRunning do
|
||||
|
||||
local s = read( nil, tCommandHistory, function( sLine )
|
||||
if settings.get( "lua.autocomplete" ) then
|
||||
local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.]+$" )
|
||||
local nStartPos = string.find( sLine, "[a-zA-Z0-9_%.:]+$" )
|
||||
if nStartPos then
|
||||
sLine = string.sub( sLine, nStartPos )
|
||||
end
|
||||
@ -64,10 +64,10 @@ while bRunning do
|
||||
end
|
||||
|
||||
if func then
|
||||
local tResults = { pcall( func ) }
|
||||
local tResults = table.pack( pcall( func ) )
|
||||
if tResults[1] then
|
||||
local n = 1
|
||||
while (tResults[n + 1] ~= nil) or (n <= nForcePrint) do
|
||||
while n < tResults.n or (n <= nForcePrint) do
|
||||
local value = tResults[ n + 1 ]
|
||||
if type( value ) == "table" then
|
||||
local metatable = getmetatable( value )
|
||||
|
@ -0,0 +1,6 @@
|
||||
local ok, err = pcall( pocket.equipBack )
|
||||
if not ok then
|
||||
printError( "Nothing to equip" )
|
||||
else
|
||||
print( "Item equipped" )
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
local ok, err = pcall( pocket.unequipBack )
|
||||
if not ok then
|
||||
printError( "Nothing to unequip" )
|
||||
else
|
||||
print( "Item unequipped" )
|
||||
end
|
@ -310,14 +310,14 @@ elseif sCommand == "join" then
|
||||
function printMessage( sMessage )
|
||||
term.redirect( historyWindow )
|
||||
print()
|
||||
if string.match( sMessage, "^\*" ) then
|
||||
if string.match( sMessage, "^%*" ) then
|
||||
-- Information
|
||||
term.setTextColour( highlightColour )
|
||||
write( sMessage )
|
||||
term.setTextColour( textColour )
|
||||
else
|
||||
-- Chat
|
||||
local sUsernameBit = string.match( sMessage, "^\<[^\>]*\>" )
|
||||
local sUsernameBit = string.match( sMessage, "^<[^>]*>" )
|
||||
if sUsernameBit then
|
||||
term.setTextColour( highlightColour )
|
||||
write( sUsernameBit )
|
||||
|
@ -2,9 +2,12 @@
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
-- "set"
|
||||
local x,y = term.getCursorPos()
|
||||
local tSettings = {}
|
||||
for n,sName in ipairs( settings.getNames() ) do
|
||||
print( textutils.serialize(sName) .. " is " .. textutils.serialize(settings.get(sName)) )
|
||||
tSettings[n] = textutils.serialize(sName) .. " is " .. textutils.serialize(settings.get(sName))
|
||||
end
|
||||
textutils.pagedPrint(table.concat(tSettings,"\n"),y-3)
|
||||
|
||||
elseif #tArgs == 1 then
|
||||
-- "set foo"
|
||||
|
@ -31,6 +31,7 @@ local function createShellEnv( sDir )
|
||||
table = table,
|
||||
}
|
||||
package.path = "?;?.lua;?/init.lua"
|
||||
package.config = "/\n;\n?\n!\n-"
|
||||
package.preload = {}
|
||||
package.loaders = {
|
||||
function( name )
|
||||
@ -68,6 +69,9 @@ local function createShellEnv( sDir )
|
||||
|
||||
local sentinel = {}
|
||||
local function require( name )
|
||||
if type( name ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( name ) .. ")", 2 )
|
||||
end
|
||||
if package.loaded[name] == sentinel then
|
||||
error("Loop detected requiring '" .. name .. "'", 0)
|
||||
end
|
||||
@ -181,6 +185,12 @@ function shell.dir()
|
||||
end
|
||||
|
||||
function shell.setDir( _sDir )
|
||||
if type( _sDir ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sDir ) .. ")", 2 )
|
||||
end
|
||||
if not fs.isDir( _sDir ) then
|
||||
error( "Not a directory", 2 )
|
||||
end
|
||||
sDir = _sDir
|
||||
end
|
||||
|
||||
@ -189,10 +199,16 @@ function shell.path()
|
||||
end
|
||||
|
||||
function shell.setPath( _sPath )
|
||||
if type( _sPath ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sPath ) .. ")", 2 )
|
||||
end
|
||||
sPath = _sPath
|
||||
end
|
||||
|
||||
function shell.resolve( _sPath )
|
||||
if type( _sPath ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sPath ) .. ")", 2 )
|
||||
end
|
||||
local sStartChar = string.sub( _sPath, 1, 1 )
|
||||
if sStartChar == "/" or sStartChar == "\\" then
|
||||
return fs.combine( "", _sPath )
|
||||
@ -212,6 +228,9 @@ local function pathWithExtension( _sPath, _sExt )
|
||||
end
|
||||
|
||||
function shell.resolveProgram( _sCommand )
|
||||
if type( _sCommand ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sCommand ) .. ")", 2 )
|
||||
end
|
||||
-- Substitute aliases firsts
|
||||
if tAliases[ _sCommand ] ~= nil then
|
||||
_sCommand = tAliases[ _sCommand ]
|
||||
@ -327,6 +346,9 @@ local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousPar
|
||||
end
|
||||
|
||||
function shell.complete( sLine )
|
||||
if type( sLine ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( sLine ) .. ")", 2 )
|
||||
end
|
||||
if #sLine > 0 then
|
||||
local tWords = tokenise( sLine )
|
||||
local nIndex = #tWords
|
||||
@ -363,10 +385,19 @@ function shell.complete( sLine )
|
||||
end
|
||||
|
||||
function shell.completeProgram( sProgram )
|
||||
if type( sProgram ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( sProgram ) .. ")", 2 )
|
||||
end
|
||||
return completeProgram( sProgram )
|
||||
end
|
||||
|
||||
function shell.setCompletionFunction( sProgram, fnComplete )
|
||||
if type( sProgram ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( sProgram ) .. ")", 2 )
|
||||
end
|
||||
if type( fnComplete ) ~= "function" then
|
||||
error( "bad argument #2 (expected function, got " .. type( fnComplete ) .. ")", 2 )
|
||||
end
|
||||
tCompletionInfo[ sProgram ] = {
|
||||
fnComplete = fnComplete
|
||||
}
|
||||
@ -384,10 +415,19 @@ function shell.getRunningProgram()
|
||||
end
|
||||
|
||||
function shell.setAlias( _sCommand, _sProgram )
|
||||
if type( _sCommand ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sCommand ) .. ")", 2 )
|
||||
end
|
||||
if type( _sProgram ) ~= "string" then
|
||||
error( "bad argument #2 (expected string, got " .. type( _sProgram ) .. ")", 2 )
|
||||
end
|
||||
tAliases[ _sCommand ] = _sProgram
|
||||
end
|
||||
|
||||
function shell.clearAlias( _sCommand )
|
||||
if type( _sCommand ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( _sCommand ) .. ")", 2 )
|
||||
end
|
||||
tAliases[ _sCommand ] = nil
|
||||
end
|
||||
|
||||
@ -417,6 +457,9 @@ if multishell then
|
||||
end
|
||||
|
||||
function shell.switchTab( nID )
|
||||
if type( nID ) ~= "number" then
|
||||
error( "bad argument #1 (expected number, got " .. type( nID ) .. ")", 2 )
|
||||
end
|
||||
multishell.setFocus( nID )
|
||||
end
|
||||
end
|
||||
|
@ -205,6 +205,35 @@ shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completePastebin
|
||||
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completeChat )
|
||||
shell.setCompletionFunction( "rom/programs/command/exec.lua", completeExec )
|
||||
|
||||
if turtle then
|
||||
local tGoOptions = { "left", "right", "forward", "back", "down", "up" }
|
||||
local function completeGo( shell, nIndex, sText )
|
||||
return completeMultipleChoice(sText,tGoOptions)
|
||||
end
|
||||
local tTurnOptions = { "left", "right" }
|
||||
local function completeTurn( shell, nIndex, sText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tTurnOptions )
|
||||
end
|
||||
end
|
||||
local tEquipOptions = { "left", "right" }
|
||||
local function completeEquip( shell, nIndex, sText )
|
||||
if nIndex == 2 then
|
||||
return completeMultipleChoice( sText, tEquipOptions )
|
||||
end
|
||||
end
|
||||
local function completeUnequip( shell, nIndex, sText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tEquipOptions )
|
||||
end
|
||||
end
|
||||
shell.setCompletionFunction( "rom/programs/turtle/go.lua", completeGo )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completeTurn )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completeEquip )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completeUnequip )
|
||||
end
|
||||
|
||||
|
||||
-- Run autorun files
|
||||
if fs.exists( "/rom/autorun" ) and fs.isDir( "/rom/autorun" ) then
|
||||
local tFiles = fs.list( "/rom/autorun" )
|
||||
|
Loading…
Reference in New Issue
Block a user