1
0
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:
Wilma456 2017-06-29 16:24:30 +02:00 committed by GitHub
commit 845e1b633d
30 changed files with 957 additions and 482 deletions

View File

@ -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
--------------

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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 )

View File

@ -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;
}

View File

@ -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 } );
}
}
}

View File

@ -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;
}
}
}
};
}
}

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -56,6 +56,7 @@ function getNames()
for k,v in pairs( tSettings ) do
result[ #result + 1 ] = k
end
table.sort(result)
return result
end

View File

@ -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 = {}

View File

@ -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.

View File

@ -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] )

View File

@ -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

View File

@ -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, ... )

View File

@ -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)

View File

@ -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

View File

@ -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 )

View File

@ -0,0 +1,6 @@
local ok, err = pcall( pocket.equipBack )
if not ok then
printError( "Nothing to equip" )
else
print( "Item equipped" )
end

View File

@ -0,0 +1,6 @@
local ok, err = pcall( pocket.unequipBack )
if not ok then
printError( "Nothing to unequip" )
else
print( "Item unequipped" )
end

View File

@ -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 )

View File

@ -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"

View File

@ -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

View File

@ -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" )