1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 22:17:39 +00:00

Compare commits

..

49 Commits

Author SHA1 Message Date
SquidDev
18429c50f9 Bump version 2018-05-15 11:59:21 +01:00
SquidDev
7ec8ddcf7d Delete previous chat messages of the same type
This uses a custom ComputerCraft packet to send chat messages to the
client. When received, we delete all messages of the same category
before sending the new ones.

This avoids cluttering the chat with near-identical messages, and helps
make working with the "individual dump" command easier, as the previous
computer's dump output is deleted.

Also change the max height of the TextTable to 18, so it fits within
Minecraft's default chat limit.
2018-05-15 11:44:23 +01:00
SquidDev
5bf9f9e3c5 Allow multiple HTTP request methods
This implements an argument format similar to LuaReqeust, as described
in dan200/ComputerCraft#515. The Lua argument checking code is a little
verbose and repetitive, but I'm not sure how to avoid that - we should
look into improving it in the future.

Closes #21
2018-05-15 10:11:08 +01:00
hugeblank
e4164ee9a1 amend additional typo in keys.lua
- fixed circumflex typo: you can now cîrcûmflêx on all your friends
- added comment making it clearer that the following lines are intended for backwards compatibility.
2018-05-14 15:54:06 +01:00
SquidDev
b522af3075 Add some basic tracking for HTTP requests and websockets
This should allow for easier identification of misbehaving computers,
which are consuming a large amount of bandwidth.
2018-05-14 15:52:16 +01:00
SquidDev
8775052dee Merge pull request #543 from hugeblank/ComputerCraft/patch-1
amend typo in keys.lua
2018-05-08 09:46:14 +01:00
hugeblank
c0c5d57e10 amend typo in keys.lua
The most important commit that CC has ever seen.
2018-05-08 01:10:45 -07:00
SquidDev
4c2e97b1af Enable compression extension for websockets 2018-05-05 08:34:35 +01:00
SquidDev
f17df15117 Always create extended disks
Whilst the legacy ones are important for backwards compatibility, they
cannot have an ID of 0, which introduces issues when they are the first
disk created in the world.
2018-04-27 09:51:28 +01:00
SquidDev
bfbb18bdfc Add support for tracking server-thread tasks too
This allows us to track how much work various peripherals are doing.
This will not work with all systems, such as Plethora, as that has its
own execution system.
2018-04-24 12:26:26 +01:00
SquidDev
cac65ef755 Print a useful debug log if we cannot safely terminate a task 2018-04-24 11:07:33 +01:00
SquidDev
a42793024b Merge pull request #541 from SquidDev-CC/ComputerCraft/feature/unlimited-reads
Remove upper bound on how many bytes/characters can be read
2018-04-23 16:42:28 +01:00
SquidDev
7dbc4e6455 Remove upper bound on how many bytes/characters can be read
The limit was added to prevent people creating arbitrarily large buffers
(for instance, handle.read(2^31) would create a 2GB char array). For
"large" counts, we now read in blocks of size 8192, adding to an
extendable buffer.
2018-04-23 16:41:00 +01:00
SquidDev
a0d71cb3ad A couple of fixes for maven and CCEmuX
- Add additional maven metadata and strip dependencies
 - Shift ICommand registration into the proxy, to avoid class loading
   issues. This is probably rather temperamental, but classloading
   always is.
2018-04-20 19:39:53 +01:00
SquidDev
83546d0acb Add support for Maven uploading 2018-04-19 22:36:00 +01:00
SquidDev
e2f9ddd534 Add support for arbitrary trackers
This is used by cc-prometheus to monitor statistics. It isn't currently
exposed as an API, as I'm making no guarantees on the stability of this.
2018-04-19 21:59:35 +01:00
SquidDev
911e404bfa Add support for tracking fs, turtle and peripheral operations
See #25
2018-04-17 09:22:26 +01:00
SquidDev
bfeafe163f Various improvements to the tracking system
- Trackers are created per-user, meaning multiple people can run
   /computercraft track at once.
 - Allow sorting the tracking information by arbitrary fields.
 - Add support for tracking arbitrary fields (though none are currently
   implemented).
2018-04-16 21:06:16 +01:00
SquidDev
6cf32f1f74 Various improvements to peripheral invalidation
- Abstract peripheral ID and type checking into separate class
 - Update peripherals directly rather than marking as invalid then
   fetching from the network.
 - Update peripherals when adjacent tiles change

This does result in a slightly more ugly interface, but reduces the
amount of work needed to perform partial updates of peripherals, such as
those done by neighbouring tile updates.
2018-04-16 18:22:28 +01:00
SquidDev
04f162ef25 Rename node → m_node
It's a bit of an ugly naming convention, but it's consistent.
2018-04-16 17:10:08 +01:00
SquidDev
b2aa390ae1 Split up modem and connection invalidation 2018-04-16 17:02:20 +01:00
SquidDev
6ca61f000f Minor performance improvements to WiredNetworkChange
This should improve the performance of the common case, where one
peripheral set is empty.
2018-04-16 15:31:37 +01:00
SquidDev
20a47a7f88 Merge pull request #540 from SquidDev-CC/ComputerCraft/hotfix/fakeplayer-stubs
Add additional method stubs to the TurtlePlayer
2018-04-16 09:39:15 +01:00
SquidDev
e2e6946c92 Add additional method stubs to the TurtlePlayer
Forge's default fake player implementation doesn't override all methods
which use the connection. As it is not set, we get an NPE and thus crash
the server. We simply stub those methods out ourselves to prevent such
an issue.
2018-04-16 09:34:44 +01:00
SquidDev
abe917cd54 Fix peripherals showing up when they shouldn't on world load
When initially attaching a modem, the adjacent computer would not show
up on its own peripheral list (like in vanilla CC). However, it would
show up when the chunk was reloaded as peripherals were added through a
different method.

This prevents such behaviour, always hiding the remote peripheral from
the object which provides it.

Closes #20
2018-04-07 10:23:11 +01:00
SquidDev
a1d77ab8e7 Merge pull request #536 from Luca0208/ComputerCraft/master
Make wget automatically determine the file name.
2018-04-06 21:08:54 +01:00
SquidDev
c8db671409 Override monitors' lightmap coordinates
Shaders appear to ignore all the other subtle (and not-so-subtle) hints
we drop that monitors shouldn't be rendered with shadows. This solution
isn't optimal, as monitors may still be tinted due to sunlight, but
there is nothing we can do about that.

Many thanks to ferreusveritas for their help in diagnosing, fixing and
testing this issue.
2018-03-30 12:57:27 +01:00
SquidDev
52641b7bea Allow rendering a monitor tile multiple times in a tick
Shader mods may perform multiple passes when rendering a tile, so
monitors will be drawn transparently on later passes. In order to
prevent this we allow drawing the a single tile multiple times in a
tick.
2018-03-29 12:31:20 +01:00
SquidDev
3e751ee94a Use Unicode escape code instead of literal
It doesn't compile under Gradle on my system. Goodness knows why not.
2018-03-29 11:47:45 +01:00
SquidDev
2b28cc3558 Merge remote-tracking branch 'SquidDev-CC-ComputerCraft/hotfix/disk-drive-stop' 2018-03-29 11:41:00 +01:00
SquidDev
f9761388b1 Fix .isDiskPresent() always reporting true
We were still determining if the stack was null, but post-1.11 this will
never be the case.
2018-03-29 11:36:59 +01:00
SquidDev
d28694eb57 Rewrite the command system's text padder
This uses narrow near-invisible unicode characters instead of attempting
to toggle between bold and non-bold spaces.
2018-03-25 21:31:31 +01:00
SquidDev
d758895578 Bump to Forge 1.12.2
Nobody is still using 1.12, so we might as well update to the latest -
it makes it easier to test mod compatibility at least.
2018-03-24 12:20:51 +00:00
SquidDev
043d5f00ca Convert wired elements to capabilities
See #18
2018-03-24 11:57:36 +00:00
SquidDev
36878e75b7 Merge remote-tracking branch 'origin/master' 2018-03-15 17:53:21 +00:00
SquidDev
ebb50cba48 Bump version 2018-03-15 17:43:23 +00:00
SquidDev
7c218361d9 Merge pull request #528 from SquidDev-CC/ComputerCraft/feature/computer-upgrade
Add recipes to upgrade computers
2018-03-15 17:39:17 +00:00
SquidDev
bb2eab0bed Use the ore dictionary for upgrade recipes
See #517 for motivation
2018-03-15 17:34:58 +00:00
SquidDev
e8c0cf3857 Convert TurtleRecipe to use ComputerConvertRecipe
The two recipes are pretty similar, so this allows us to substantially
simplify the code. This now introduces the additional requirement that
computers must be used to create turtles, rather than pocket computers
or another turtle.
2018-03-13 14:21:39 +00:00
SquidDev
db825a7aab Add recipes to convert computer items into their upgraded counterparts
This adds IComputerItem.withFamily(ItemStack, ComputerFamily) as well as
a ComputerFamilyRecipe class. Each type of computer (normal, turtle,
pocket) defines a recipe using this class, as they require a different
number of gold ingots to upgrade.
2018-03-13 14:14:36 +00:00
SquidDev
dbcae810f0 Fix monitor.getTextScale() being doubled
Closes #16. The original method divided by 2 in the getter, but that
was removed in our monitor rewrite.
2018-03-10 22:16:15 +00:00
Daniel Ratcliffe
914df8b0c7 Merge pull request #521 from SquidDev-CC/hotfix/modem-full-block
Fix wireless modems suffocating entities
2018-03-05 14:07:57 +00:00
SquidDev
f753513289 Wrap ComputerCraft fake player with brackets
Hopefully this'll make GriefPrevention play a little nicer
2018-03-01 15:58:09 +00:00
SquidDev
7bb8efed1d Dispose of monitor display lists when unloading worlds
This means all display lists are correctly deleted when changing
dimensions/exiting to main menu.
2018-02-24 20:37:14 +00:00
SquidDev
0cec4aee8c Merge pull request #15 from Lignum/maybe-fix-monitors
Maybe fix monitors
2018-02-24 16:59:21 +00:00
Lignum
e0c9dc24e7 Generate individual display lists instead of relying on their names being contiguous 2018-02-24 17:21:12 +01:00
SquidDev
244907a39a Various cherry picks from world thread safety
- Ensure monitor draw lists are not reused after deleting them.
 - Make terminal methods synchronized, avoding NPEs and IOOBEs when
   resizing.
2018-02-22 21:20:08 +00:00
SquidDev
5eadf5533d Fix wireless modems suffocating entities
As of #458, BlockPeripheral will act as a full/opaque block for some
peripherals and a transparent one for others. However, some Block
methods use the default state rather than the current one. This means
modems report being a full block when they are not, leading to
suffocating entities and lighting glitches.
2018-02-15 18:17:02 +00:00
SquidDev
1ef7c8e8db Only send the packet to people within 64 blocks
This is equivalent to what vanilla Minecraft does
2017-09-12 22:44:49 +01:00
84 changed files with 2387 additions and 958 deletions

View File

@@ -15,18 +15,20 @@ buildscript {
}
plugins {
id 'com.matthewprenger.cursegradle' version '1.0.9'
id 'com.matthewprenger.cursegradle' version '1.0.10'
}
apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
version = "1.80pr1.4"
version = "1.80pr1.6"
group = "org.squiddev"
archivesBaseName = "cc-tweaked"
minecraft {
version = "1.12-14.21.1.2387"
version = "1.12.2-14.23.2.2634"
runDir = "run"
replace '${version}', project.version
@@ -35,7 +37,7 @@ minecraft {
// stable_# stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not allways work.
// simply re-run your setup task after changing the mappings to update your workspace.
mappings = "snapshot_20170629"
mappings = "snapshot_20180324"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}
@@ -53,14 +55,17 @@ repositories {
configurations {
shade
compile.extendsFrom shade
deployerJars
}
dependencies {
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
runtime "mezz.jei:jei_1.12:4.7.5.86"
deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
shade 'org.squiddev:Cobalt:0.3.1'
testCompile 'junit:junit:4.11'
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
}
javadoc {
@@ -125,6 +130,59 @@ curseforge {
}
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourceJar
}
}
}
uploadArchives {
repositories {
if(project.hasProperty('mavenUploadUrl')) {
mavenDeployer {
configuration = configurations.deployerJars
repository(url: project.property('mavenUploadUrl')) {
authentication(
userName: project.property('mavenUploadUser'),
privateKey: project.property('mavenUploadKey'))
}
pom.project {
name 'CC: Tweaked'
packaging 'jar'
description 'A fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development features of the mod.'
url 'https://github.com/SquidDev-CC/CC-Tweaked'
scm {
url 'https://github.com/dan200/ComputerCraft.git'
}
issueManagement {
system 'github'
url 'https://github.com/dan200/ComputerCraft/issues'
}
licenses {
license {
name 'ComputerCraft Public License, Version 1.0'
url 'https://github.com/dan200/ComputerCraft/blob/master/LICENSE'
distribution 'repo'
}
}
}
pom.whenConfigured { pom ->
pom.dependencies.clear()
}
}
}
}
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint"

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'cc-tweaked'

View File

@@ -16,7 +16,6 @@ import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredProvider;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
@@ -29,6 +28,7 @@ import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputer;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
@@ -61,6 +61,7 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity;
@@ -69,6 +70,7 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketBuffer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
@@ -266,7 +268,6 @@ public class ComputerCraft
private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>();
private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>();
private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>();
private static final Set<IWiredProvider> wiredProviders = new LinkedHashSet<>();
// Implementation
@Mod.Instance( value = ComputerCraft.MOD_ID )
@@ -461,8 +462,7 @@ public class ComputerCraft
@Mod.EventHandler
public void onServerStarting( FMLServerStartingEvent event )
{
event.registerServerCommand( new CommandComputer() );
event.registerServerCommand( new CommandComputerCraft() );
proxy.initServer( event.getServer() );
}
@Mod.EventHandler
@@ -472,6 +472,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
}
@@ -482,6 +483,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
}
@@ -505,11 +507,6 @@ public class ComputerCraft
return proxy.getRenderFrame();
}
public static void deleteDisplayLists( int list, int range )
{
proxy.deleteDisplayLists( list, range );
}
public static Object getFixedWidthFontRenderer()
{
return proxy.getFixedWidthFontRenderer();
@@ -738,11 +735,6 @@ public class ComputerCraft
}
}
public static void registerWiredProvider( IWiredProvider provider )
{
if( provider != null ) wiredProviders.add( provider );
}
public static IWiredNode createWiredNodeForElement( IWiredElement element )
{
return new WiredNode( element );
@@ -771,20 +763,10 @@ public class ComputerCraft
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
{
// Try the handlers in order:
for( IWiredProvider provider : wiredProviders )
{
try
{
IWiredElement element = provider.getElement( world, pos, side );
if( element != null ) return element;
}
catch( Exception e )
{
ComputerCraft.log.error( "Wired element provider " + provider + " errored.", e );
}
}
return null;
TileEntity tile = world.getTileEntity( pos );
return tile != null && tile.hasCapability( CapabilityWiredElement.CAPABILITY, side )
? tile.getCapability( CapabilityWiredElement.CAPABILITY, side )
: null;
}
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )

View File

@@ -14,7 +14,6 @@ import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredProvider;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
@@ -332,26 +331,6 @@ public final class ComputerCraftAPI
}
}
/**
* Registers a peripheral handler to convert blocks into {@link IPeripheral} implementations.
*
* @param handler The peripheral provider to register.
* @see dan200.computercraft.api.peripheral.IPeripheral
* @see dan200.computercraft.api.peripheral.IPeripheralProvider
*/
public static void registerWiredProvider( @Nonnull IWiredProvider handler )
{
findCC();
if ( computerCraft_registerWiredProvider != null)
{
try {
computerCraft_registerWiredProvider.invoke( null, handler );
} catch (Exception e){
// It failed
}
}
}
/**
* Construct a new wired node for a given wired element
*
@@ -453,9 +432,6 @@ public final class ComputerCraftAPI
computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
ILuaAPIFactory.class
} );
computerCraft_registerWiredProvider = findCCMethod( "registerWiredProvider", new Class<?>[] {
IWiredProvider.class
} );
computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
IWiredElement.class
} );
@@ -499,7 +475,6 @@ public final class ComputerCraftAPI
private static Method computerCraft_registerPocketUpgrade = null;
private static Method computerCraft_getWirelessNetwork = null;
private static Method computerCraft_registerAPIFactory = null;
private static Method computerCraft_registerWiredProvider = null;
private static Method computerCraft_createWiredNodeForElement = null;
private static Method computerCraft_getWiredElementAt = null;
}

View File

@@ -1,11 +1,8 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;
/**
* An object which may be part of a wired network.
@@ -14,30 +11,11 @@ import java.util.Map;
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
*
* Elements are generally tied to a block or tile entity in world. One should either register an {@link IWiredProvider}
* or implement {@link IWiredElementTile} on your tile entity.
*
* @see IWiredProvider
* @see ComputerCraftAPI#registerWiredProvider(IWiredProvider)
* @see IWiredElementTile
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* {@link IWiredElement} capability for the appropriate sides.
*/
public interface IWiredElement extends IWiredSender
{
/**
* Fetch the peripherals this network element provides.
*
* This is only called when initially attaching to a network and after a call to {@link IWiredNode#invalidate()}}, so
* one does not <em>need</em> to cache the return value.
*
* @return The peripherals this node provides.
* @see IWiredNode#invalidate()
*/
@Nonnull
default Map<String, IPeripheral> getPeripherals()
{
return Collections.emptyMap();
}
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.

View File

@@ -1,22 +0,0 @@
package dan200.computercraft.api.network.wired;
import net.minecraft.util.EnumFacing;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link net.minecraft.tileentity.TileEntity} which provides a {@link IWiredElement}. This acts
* as a simpler alternative to a full-blown {@link IWiredProvider}.
*/
public interface IWiredElementTile
{
/**
* Get the wired element of this tile for a given side.
*
* @param side The side to get the network element from.
* @return A network element, or {@code null} if there is no element here.
*/
@Nullable
IWiredElement getWiredElement( @Nonnull EnumFacing side );
}

View File

@@ -1,6 +1,9 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
@@ -51,7 +54,8 @@ public interface IWiredNetwork
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread.
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
@@ -62,13 +66,15 @@ public interface IWiredNetwork
boolean remove( @Nonnull IWiredNode node );
/**
* Mark this node's peripherals as having changed.
* Update the peripherals a node provides.
*
* This should only be used on the server thread.
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to mark as invalid.
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredElement#getPeripherals()
* @see IWiredNode#updatePeripherals(Map)
*/
void invalidate( @Nonnull IWiredNode node );
void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> peripherals );
}

View File

@@ -1,8 +1,10 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
@@ -72,7 +74,8 @@ public interface IWiredNode extends IPacketNetwork
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread.
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
@@ -87,12 +90,14 @@ public interface IWiredNode extends IPacketNetwork
/**
* Mark this node's peripherals as having changed.
*
* This should only be used on the server thread.
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @see IWiredElement#getPeripherals()
* @param peripherals The new peripherals for this node.
* @see IWiredNetwork#updatePeripherals(IWiredNode, Map)
*/
default void invalidate()
default void updatePeripherals( @Nonnull Map<String, IPeripheral> peripherals )
{
getNetwork().invalidate( this );
getNetwork().updatePeripherals( this, peripherals );
}
}

View File

@@ -1,29 +0,0 @@
package dan200.computercraft.api.network.wired;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Fetch or create an {@link IWiredElement} for a block at a given position.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerWiredProvider(IWiredProvider)
* @see IWiredElementTile
*/
@FunctionalInterface
public interface IWiredProvider
{
/**
* Extract a wired network element from a block location.
*
* @param world The world the block is in.
* @param pos The position the block is at.
* @param side The side to get the network element from.
* @return A network element, or {@code null} if there is not an element here you'd like to handle.
*/
@Nullable
IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side );
}

View File

@@ -210,7 +210,7 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
private static class TurtleItemColour implements IItemColor
{
@Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int tintIndex )
public int colorMultiplier( @Nonnull ItemStack stack, int tintIndex )
{
if( tintIndex == 0 )
{

View File

@@ -25,6 +25,7 @@ import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
@@ -33,9 +34,10 @@ import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
import dan200.computercraft.shared.util.Colour;
import gnu.trove.map.hash.TIntIntHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.renderer.block.model.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
@@ -50,6 +52,7 @@ import net.minecraft.util.IThreadListener;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
@@ -57,6 +60,7 @@ import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -70,6 +74,8 @@ import java.util.List;
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{
private static TIntIntHashMap lastCounts = new TIntIntHashMap();
private long m_tick;
private long m_renderFrame;
private FixedWidthFontRenderer m_fixedWidthFontRenderer;
@@ -297,12 +303,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
return m_renderFrame;
}
@Override
public void deleteDisplayLists( int list, int range )
{
GlStateManager.glDeleteLists( list, range );
}
@Override
public Object getFixedWidthFontRenderer()
{
@@ -389,6 +389,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
case ComputerCraftPacket.ComputerTerminalChanged:
case ComputerCraftPacket.ComputerDeleted:
case ComputerCraftPacket.PlayRecord:
case ComputerCraftPacket.PostChat:
{
// Packet from Server to Client
IThreadListener listener = Minecraft.getMinecraft();
@@ -455,6 +456,36 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{
mc.world.playRecord( pos, null );
}
break;
}
case ComputerCraftPacket.PostChat:
{
/*
This allows us to send delete chat messages of the same "category" as the previous one.
It's used by the various /computercraft commands to avoid filling the chat with repetitive
messages.
*/
int id = packet.m_dataInt[0];
ITextComponent[] components = new ITextComponent[packet.m_dataString.length];
for( int i = 0; i < packet.m_dataString.length; i++ )
{
components[i] = ITextComponent.Serializer.jsonToComponent( packet.m_dataString[i] );
}
GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI();
// Keep track of how many lines we wrote last time, deleting any extra ones.
int lastCount = lastCounts.get( id );
for( int i = components.length; i < lastCount; i++ ) chat.deleteChatLine( i + id );
lastCounts.put( id, components.length );
// Add new lines
for( int i = 0; i < components.length; i++ )
{
chat.printChatMessageWithOptionalDeletion( components[i], id + i );
}
break;
}
}
@@ -552,6 +583,15 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
m_renderFrame++;
}
}
@SubscribeEvent
public void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isRemote )
{
ClientMonitor.destroyAll();
}
}
}
@SideOnly(Side.CLIENT)
@@ -565,7 +605,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
}
@Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int layer )
public int colorMultiplier( @Nonnull ItemStack stack, int layer )
{
return layer == 0 ? 0xFFFFFF : disk.getColour( stack );
}

View File

@@ -189,7 +189,7 @@ public final class ModelTransformer
private BakedQuadBuilder( VertexFormat format )
{
this.format = format;
this.vertexData = new int[ format.getNextOffset() ];
this.vertexData = new int[ format.getSize() ];
}
@Nonnull

View File

@@ -18,6 +18,7 @@ import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
@@ -47,19 +48,20 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( originTerminal == null ) return;
TileMonitor origin = originTerminal.getOrigin();
BlockPos monitorPos = monitor.getPos();
// Ensure each monitor is rendered only once
// Ensure each monitor terminal is rendered only once. We allow rendering a specific tile
// multiple times in a single frame to ensure compatibility with shaders which may run a
// pass multiple times.
long renderFrame = ComputerCraft.getRenderFrame();
if( originTerminal.lastRenderFrame == renderFrame )
if( originTerminal.lastRenderFrame == renderFrame && !monitorPos.equals( originTerminal.lastRenderPos ) )
{
return;
}
else
{
originTerminal.lastRenderFrame = renderFrame;
}
BlockPos monitorPos = monitor.getPos();
originTerminal.lastRenderFrame = renderFrame;
originTerminal.lastRenderPos = monitorPos;
BlockPos originPos = origin.getPos();
posX += originPos.getX() - monitorPos.getX();
posY += originPos.getY() - monitorPos.getY();
@@ -96,6 +98,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
// Draw the contents
GlStateManager.depthMask( false );
OpenGlHelper.setLightmapTextureCoords( OpenGlHelper.lightmapTexUnit, 0xFF, 0xFF );
GlStateManager.disableLighting();
mc.entityRenderer.disableLightmap();
try
@@ -106,9 +109,9 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
Palette palette = terminal.getPalette();
// Allocate display lists
if( originTerminal.renderDisplayList < 0 )
if( originTerminal.renderDisplayLists == null )
{
originTerminal.renderDisplayList = GlStateManager.glGenLists( 3 );
originTerminal.createLists();
redraw = true;
}
@@ -132,7 +135,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build background display list
GlStateManager.glNewList( originTerminal.renderDisplayList, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
try
{
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
@@ -171,7 +174,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList();
}
}
GlStateManager.callList( originTerminal.renderDisplayList );
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
GlStateManager.resetColor();
// Draw text
@@ -179,7 +182,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build text display list
GlStateManager.glNewList( originTerminal.renderDisplayList + 1, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
try
{
// Lines
@@ -199,7 +202,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList();
}
}
GlStateManager.callList( originTerminal.renderDisplayList + 1 );
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
GlStateManager.resetColor();
// Draw cursor
@@ -207,7 +210,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build cursor display list
GlStateManager.glNewList( originTerminal.renderDisplayList + 2, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
try
{
// Cursor
@@ -233,7 +236,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
}
if( ComputerCraft.getGlobalCursorBlink() )
{
GlStateManager.callList( originTerminal.renderDisplayList + 2 );
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
GlStateManager.resetColor();
}
}

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import java.io.InputStream;
@@ -88,6 +89,7 @@ public class FSAPI implements ILuaAPI
{
// list
String path = getString( args, 0 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try {
String[] results = m_fileSystem.list( path );
Map<Object,Object> table = new HashMap<>();
@@ -162,6 +164,7 @@ public class FSAPI implements ILuaAPI
// makeDir
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path );
return null;
} catch( FileSystemException e ) {
@@ -174,6 +177,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 );
String dest = getString( args, 1 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest );
return null;
} catch( FileSystemException e ) {
@@ -186,6 +190,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 );
String dest = getString( args, 1 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest );
return null;
} catch( FileSystemException e ) {
@@ -197,6 +202,7 @@ public class FSAPI implements ILuaAPI
// delete
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path );
return null;
} catch( FileSystemException e ) {
@@ -208,6 +214,7 @@ public class FSAPI implements ILuaAPI
// open
String path = getString( args, 0 );
String mode = getString( args, 1 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try {
switch( mode )
{
@@ -288,6 +295,7 @@ public class FSAPI implements ILuaAPI
// find
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
String[] results = m_fileSystem.find( path );
Map<Object,Object> table = new HashMap<>();
for(int i=0; i<results.length; ++i ) {

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis;
import com.google.common.collect.ImmutableSet;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
@@ -21,9 +22,14 @@ import java.util.*;
import java.util.concurrent.Future;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI
{
private static final Set<String> HTTP_METHODS = ImmutableSet.of(
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"
);
private final IAPIEnvironment m_apiEnvironment;
private final List<Future<?>> m_httpTasks;
private final Set<Closeable> m_closeables;
@@ -38,7 +44,7 @@ public class HTTPAPI implements ILuaAPI
@Override
public String[] getNames()
{
return new String[] {
return new String[]{
"http"
};
}
@@ -59,7 +65,7 @@ public class HTTPAPI implements ILuaAPI
}
@Override
public void shutdown( )
public void shutdown()
{
synchronized( m_httpTasks )
{
@@ -89,7 +95,7 @@ public class HTTPAPI implements ILuaAPI
@Override
public String[] getMethodNames()
{
return new String[] {
return new String[]{
"request",
"checkURL",
"websocket",
@@ -101,52 +107,68 @@ public class HTTPAPI implements ILuaAPI
{
switch( method )
{
case 0:
case 0: // request
{
// request
// Get URL
String urlString = getString( args, 0 );
String urlString, postString, requestMethod;
Map<Object, Object> headerTable;
boolean binary, redirect;
// Get POST
String postString = optString( args, 1, null );
// Get Headers
Map<String, String> headers = null;
Map<Object, Object> table = optTable( args, 2, null );
if( table != null )
if( args.length >= 1 && args[0] instanceof Map )
{
headers = new HashMap<>( table.size() );
for( Object key : table.keySet() )
Map<?, ?> options = (Map) args[0];
urlString = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", null );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
urlString = getString( args, 0 );
postString = optString( args, 1, null );
headerTable = optTable( args, 2, null );
binary = optBoolean( args, 3, false );
requestMethod = null;
redirect = true;
}
Map<String, String> headers = null;
if( headerTable != null )
{
headers = new HashMap<>( headerTable.size() );
for( Object key : headerTable.keySet() )
{
Object value = table.get( key );
Object value = headerTable.get( key );
if( key instanceof String && value instanceof String )
{
headers.put( (String)key, (String)value );
headers.put( (String) key, (String) value );
}
}
}
// Get binary
boolean binary = false;
if( args.length >= 4 )
if( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) )
{
binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE );
throw new LuaException( "Unsupported HTTP method" );
}
// Make the request
try
{
URL url = HTTPRequest.checkURL( urlString );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary, requestMethod, redirect );
synchronized( m_httpTasks )
{
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
}
return new Object[] { true };
return new Object[]{ true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return new Object[]{ false, e.getMessage() };
}
}
case 1:
@@ -164,11 +186,11 @@ public class HTTPAPI implements ILuaAPI
{
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
}
return new Object[] { true };
return new Object[]{ true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return new Object[]{ false, e.getMessage() };
}
}
case 2: // websocket
@@ -201,11 +223,11 @@ public class HTTPAPI implements ILuaAPI
{
m_httpTasks.add( connector );
}
return new Object[] { true };
return new Object[]{ true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return new Object[]{ false, e.getMessage() };
}
}
default:

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
public interface IAPIEnvironment
{
@@ -42,4 +43,11 @@ public interface IAPIEnvironment
String getLabel();
void setLabel( String label );
void addTrackingChange( TrackingField field, long change );
default void addTrackingChange( TrackingField field )
{
addTrackingChange( field, 1 );
}
}

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -109,6 +110,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
if( method >= 0 )
{
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return m_peripheral.callMethod( this, context, method, arguments );
}
else

View File

@@ -0,0 +1,214 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Various helpers for tables
*/
public final class TableHelper
{
private TableHelper()
{
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
return badKey( key, expected, ArgumentHelper.getType( actual ) );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad field '" + key + "' (" + expected + " expected, got " + actual + ")" );
}
public static double getNumberField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int getIntField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double getRealField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
return checkReal( key, getNumberField( table, key ) );
}
public static boolean getBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
@Nonnull
public static String getStringField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
@Nonnull
public static Map<Object, Object> getTableField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
public static double optNumberField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int optIntField( @Nonnull Map<?, ?> table, @Nonnull String key, int def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double optRealField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
return checkReal( key, optNumberField( table, key, def ) );
}
public static boolean optBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key, boolean def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
public static String optStringField( @Nonnull Map<?, ?> table, @Nonnull String key, String def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTableField( @Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
private static double checkReal( @Nonnull String key, double value ) throws LuaException
{
if( Double.isNaN( value ) )
{
throw badKey( key, "number", "nan" );
}
else if( value == Double.POSITIVE_INFINITY )
{
throw badKey( key, "number", "inf" );
}
else if( value == Double.NEGATIVE_INFINITY )
{
throw badKey( key, "number", "-inf" );
}
else
{
return value;
}
}
}

View File

@@ -5,6 +5,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@@ -13,6 +14,8 @@ import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
public class BinaryInputHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final InputStream m_stream;
public BinaryInputHandle( InputStream reader )
@@ -45,16 +48,46 @@ public class BinaryInputHandle extends HandleGeneric
if( args.length > 0 && args[ 0 ] != null )
{
int count = getInt( args, 0 );
if( count <= 0 || count >= 1024 * 16 )
if( count < 0 )
{
throw new LuaException( "Count out of range" );
// Whilst this may seem absurd to allow reading 0 bytes, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
byte[] bytes = new byte[ count ];
int read = m_stream.read( bytes );
byte[] bytes = new byte[ count ];
count = m_stream.read( bytes );
if( count < 0 ) return null;
if( count < bytes.length ) bytes = Arrays.copyOf( bytes, count );
return new Object[] { bytes };
if( read < 0 ) return null;
if( read < count ) bytes = Arrays.copyOf( bytes, read );
return new Object[] { bytes };
}
else
{
byte[] buffer = new byte[ BUFFER_SIZE ];
// Read the initial set of bytes, failing if none are read.
int read = m_stream.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
ByteArrayOutputStream out = new ByteArrayOutputStream( read );
count -= read;
out.write( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= buffer.length && count > 0 )
{
read = m_stream.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.write( buffer, 0, read );
}
return new Object[] { out.toByteArray() };
}
}
else
{

View File

@@ -6,10 +6,12 @@ import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class EncodedInputHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final BufferedReader m_reader;
public EncodedInputHandle( BufferedReader reader )
@@ -111,15 +113,45 @@ public class EncodedInputHandle extends HandleGeneric
try
{
int count = optInt( args, 0, 1 );
if( count <= 0 || count >= 1024 * 16 )
if( count < 0 )
{
throw new LuaException( "Count out of range" );
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of characters" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
char[] chars = new char[ count ];
int read = m_reader.read( chars );
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
}
else
{
// If we've got a large count, read in bunches of 8192.
char[] buffer = new char[ BUFFER_SIZE ];
// Read the initial set of characters, failing if none are read.
int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
StringBuilder out = new StringBuilder( read );
count -= read;
out.append( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= BUFFER_SIZE && count > 0 )
{
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.append( buffer, 0, read );
}
return new Object[] { out.toString() };
}
char[] bytes = new char[ count ];
count = m_reader.read( bytes );
if( count < 0 ) return null;
String str = new String( bytes, 0, count );
return new Object[] { str };
}
catch( IOException e )
{

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import java.io.*;
@@ -78,8 +79,10 @@ public class HTTPRequest implements Runnable
private final String m_postText;
private final Map<String, String> m_headers;
private boolean m_binary;
private final String m_method;
private final boolean m_followRedirects;
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary, final String method, final boolean followRedirects ) throws HTTPRequestException
{
m_environment = environment;
m_urlString = urlString;
@@ -87,6 +90,8 @@ public class HTTPRequest implements Runnable
m_binary = binary;
m_postText = postText;
m_headers = headers;
m_method = method;
m_followRedirects = followRedirects;
}
@Override
@@ -101,7 +106,7 @@ public class HTTPRequest implements Runnable
{
// Queue the failure event if not.
String error = e.getMessage();
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, error == null ? "Could not connect" : error, null } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, error == null ? "Could not connect" : error, null } );
return;
}
@@ -119,6 +124,8 @@ public class HTTPRequest implements Runnable
{
connection.setRequestMethod( "GET" );
}
if( m_method != null ) connection.setRequestMethod( m_method );
connection.setInstanceFollowRedirects( m_followRedirects );
// Set headers
connection.setRequestProperty( "accept-charset", "UTF-8" );
@@ -134,6 +141,11 @@ public class HTTPRequest implements Runnable
}
}
// Add request size and count to the tracker before opening the connection
m_environment.addTrackingChange( TrackingField.HTTP_REQUESTS );
m_environment.addTrackingChange( TrackingField.HTTP_UPLOAD,
getHeaderSize( connection.getRequestProperties() ) + (m_postText == null ? 0 : m_postText.length()) );
// Send POST text
if( m_postText != null )
{
@@ -178,6 +190,9 @@ public class HTTPRequest implements Runnable
headers.put( header.getKey(), joiner.join( header.getValue() ) );
}
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
getHeaderSize( connection.getHeaderFields() ) + result.length );
InputStream contents = new ByteArrayInputStream( result );
ILuaObject stream = wrapStream(
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
@@ -189,17 +204,17 @@ public class HTTPRequest implements Runnable
// Queue the appropriate event.
if( responseSuccess )
{
m_environment.queueEvent( "http_success", new Object[] { m_urlString, stream } );
m_environment.queueEvent( "http_success", new Object[]{ m_urlString, stream } );
}
else
{
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", stream } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", stream } );
}
}
catch( IOException e )
{
// There was an error
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", null } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", null } );
}
}
@@ -209,8 +224,8 @@ public class HTTPRequest implements Runnable
final int methodOffset = oldMethods.length;
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
newMethods[ methodOffset + 0 ] = "getResponseCode";
newMethods[ methodOffset + 1 ] = "getResponseHeaders";
newMethods[methodOffset + 0] = "getResponseCode";
newMethods[methodOffset + 1] = "getResponseHeaders";
return new ILuaObject()
{
@@ -233,12 +248,12 @@ public class HTTPRequest implements Runnable
case 0:
{
// getResponseCode
return new Object[] { responseCode };
return new Object[]{ responseCode };
}
case 1:
{
// getResponseHeaders
return new Object[] { responseHeaders };
return new Object[]{ responseHeaders };
}
default:
{
@@ -248,4 +263,15 @@ public class HTTPRequest implements Runnable
}
};
}
private static long getHeaderSize( Map<String, List<String>> headers )
{
long size = 0;
for( Map.Entry<String, List<String>> header : headers.entrySet() )
{
size += header.getKey() == null ? 0 : header.getKey().length();
for( String value : header.getValue() ) size += value == null ? 0 : value.length() + 1;
}
return size;
}
}

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.HTTPAPI;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.tracking.TrackingField;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -71,7 +72,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
private void onClosed()
{
close( true );
computer.queueEvent( CLOSE_EVENT, new Object[] { url } );
computer.queueEvent( CLOSE_EVENT, new Object[]{ url } );
}
@Override
@@ -102,7 +103,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
if( !handshaker.isHandshakeComplete() )
{
handshaker.finishHandshake( ch, (FullHttpResponse) msg );
computer.queueEvent( SUCCESS_EVENT, new Object[] { url, this } );
computer.queueEvent( SUCCESS_EVENT, new Object[]{ url, this } );
return;
}
@@ -115,14 +116,19 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
WebSocketFrame frame = (WebSocketFrame) msg;
if( frame instanceof TextWebSocketFrame )
{
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, ((TextWebSocketFrame) frame).text() } );
String data = ((TextWebSocketFrame) frame).text();
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, data } );
}
else if( frame instanceof BinaryWebSocketFrame )
{
ByteBuf data = frame.content();
byte[] converted = new byte[ data.readableBytes() ];
byte[] converted = new byte[data.readableBytes()];
data.readBytes( converted );
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, data } );
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, converted } );
}
else if( frame instanceof CloseWebSocketFrame )
{
@@ -135,7 +141,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
ctx.close();
computer.queueEvent( FAILURE_EVENT, new Object[] {
computer.queueEvent( FAILURE_EVENT, new Object[]{
url,
cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect"
} );
@@ -145,7 +151,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
@Override
public String[] getMethodNames()
{
return new String[] { "receive", "send", "close" };
return new String[]{ "receive", "send", "close" };
}
@Nullable
@@ -159,15 +165,16 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
{
checkOpen();
Object[] event = context.pullEvent( MESSAGE_EVENT );
if( event.length >= 3 && Objects.equal( event[ 1 ], url ) )
if( event.length >= 3 && Objects.equal( event[1], url ) )
{
return new Object[] { event[ 2 ] };
return new Object[]{ event[2] };
}
}
case 1:
{
checkOpen();
String text = arguments.length > 0 && arguments[ 0 ] != null ? arguments[ 0 ].toString() : "";
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
channel.writeAndFlush( new TextWebSocketFrame( text ) );
return null;
}

View File

@@ -21,6 +21,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
@@ -34,7 +35,7 @@ import java.security.KeyStore;
import java.util.Map;
import java.util.concurrent.Future;
/*
/**
* Provides functionality to verify and connect to a remote websocket.
*/
public final class WebsocketConnector
@@ -177,7 +178,7 @@ public final class WebsocketConnector
httpHeaders.add( header.getKey(), header.getValue() );
}
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, httpHeaders );
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, httpHeaders );
final WebsocketConnection connection = new WebsocketConnection( environment, api, handshaker, address );
new Bootstrap()
@@ -190,7 +191,12 @@ public final class WebsocketConnector
{
ChannelPipeline p = ch.pipeline();
if( ssl != null ) p.addLast( ssl.newHandler( ch.alloc(), uri.getHost(), port ) );
p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), connection );
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator( 8192 ),
WebSocketClientCompressionHandler.INSTANCE,
connection
);
}
} )
.remoteAddress( socketAddress )

View File

@@ -20,6 +20,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -167,6 +169,12 @@ public class Computer
m_computer.setLabel( label );
}
@Override
public void addTrackingChange( TrackingField field, long change )
{
Tracking.addValue( m_computer, field, change );
}
public void onPeripheralChanged( int side, IPeripheral peripheral )
{
synchronized( m_computer.m_peripherals )

View File

@@ -7,6 +7,7 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking;
import java.util.HashSet;
import java.util.Set;
@@ -68,17 +69,17 @@ public class ComputerThread
s_stopped = false;
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
{
s_threads = new Thread[ ComputerCraft.computer_threads ];
s_threads = new Thread[ComputerCraft.computer_threads];
}
SecurityManager manager = System.getSecurityManager();
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
for( int i = 0; i < s_threads.length; i++ )
{
Thread thread = s_threads[ i ];
Thread thread = s_threads[i];
if( thread == null || !thread.isAlive() )
{
thread = s_threads[ i ] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
}
@@ -223,6 +224,23 @@ public class ComputerThread
// Interrupt the thread
if( !done )
{
StringBuilder builder = new StringBuilder( "Terminating " );
if( computer != null )
{
builder.append( "computer " ).append( computer.getID() );
}
else
{
builder.append( "unknown computer" );
}
builder.append( ". Thread is currently running" );
for( StackTraceElement element : thread.getStackTrace() )
{
builder.append( "\n at " ).append( element );
}
ComputerCraft.log.error( builder.toString() );
thread.interrupt();
thread = null;
runner = null;
@@ -233,8 +251,8 @@ public class ComputerThread
{
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) ComputerTimeTracker.addTiming( computer, stop - start );
if( computer != null ) Tracking.addTaskTiming( computer, stop - start );
// Re-add it back onto the queue or remove it
synchronized( s_taskLock )
{

View File

@@ -1,116 +0,0 @@
package dan200.computercraft.core.computer;
import com.google.common.collect.MapMaker;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Tracks timing information about computers, including how long they ran for
* and the number of events they handled.
*
* Note that this <em>will</em> track computers which have been deleted (hence
* the presence of {@link #timingLookup} and {@link #timings}
*/
public class ComputerTimeTracker
{
public static class Timings
{
private final WeakReference<Computer> computer;
private final int computerId;
private int tasks;
private long totalTime;
private long maxTime;
public Timings( @Nonnull Computer computer )
{
this.computer = new WeakReference<>( computer );
this.computerId = computer.getID();
}
@Nullable
public Computer getComputer()
{
return computer.get();
}
public int getComputerId()
{
return computerId;
}
public int getTasks()
{
return tasks;
}
public long getTotalTime()
{
return totalTime;
}
public long getMaxTime()
{
return maxTime;
}
public double getAverage()
{
return totalTime / (double) tasks;
}
void update( long time )
{
tasks++;
totalTime += time;
if( time > maxTime ) maxTime = time;
}
}
private static boolean tracking;
private static final List<Timings> timings = new ArrayList<>();
private static final Map<Computer, Timings> timingLookup = new MapMaker().weakKeys().makeMap();
public synchronized static void start()
{
tracking = true;
timings.clear();
timingLookup.clear();
}
public synchronized static boolean stop()
{
if( !tracking ) return false;
tracking = false;
timingLookup.clear();
return true;
}
public static synchronized List<Timings> getTimings()
{
return new ArrayList<>( timings );
}
public static synchronized void addTiming( Computer computer, long time )
{
if( !tracking ) return;
Timings timings = ComputerTimeTracker.timingLookup.get( computer );
if( timings == null )
{
timings = new Timings( computer );
timingLookup.put( computer, timings );
ComputerTimeTracker.timings.add( timings );
}
timings.update( time );
}
}

View File

@@ -6,14 +6,17 @@
package dan200.computercraft.core.computer;
import java.util.LinkedList;
import dan200.computercraft.core.tracking.Tracking;
import java.util.ArrayDeque;
import java.util.Queue;
public class MainThread
{
private static final int MAX_TASKS_PER_TICK = 1000;
private static final int MAX_TASKS_TOTAL = 50000;
private static final LinkedList<ITask> m_outstandingTasks = new LinkedList<>();
private static final Queue<ITask> m_outstandingTasks = new ArrayDeque<>();
private static final Object m_nextUnusedTaskIDLock = new Object();
private static long m_nextUnusedTaskID = 0;
@@ -31,7 +34,7 @@ public class MainThread
{
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
{
m_outstandingTasks.addLast( task );
m_outstandingTasks.offer( task );
return true;
}
}
@@ -46,14 +49,17 @@ public class MainThread
ITask task = null;
synchronized( m_outstandingTasks )
{
if( m_outstandingTasks.size() > 0 )
{
task = m_outstandingTasks.removeFirst();
}
task = m_outstandingTasks.poll();
}
if( task != null )
{
long start = System.nanoTime();
task.execute();
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
++tasksThisTick;
}
else

View File

@@ -56,7 +56,7 @@ public class Terminal
m_palette = new Palette();
}
public void reset()
public synchronized void reset()
{
m_cursorColour = 0;
m_cursorBackgroundColour = 15;
@@ -76,7 +76,7 @@ public class Terminal
return m_height;
}
public void resize( int width, int height )
public synchronized void resize( int width, int height )
{
if( width == m_width && height == m_height )
{
@@ -189,7 +189,7 @@ public class Terminal
return m_palette;
}
public void blit( String text, String textColour, String backgroundColour )
public synchronized void blit( String text, String textColour, String backgroundColour )
{
int x = m_cursorX;
int y = m_cursorY;
@@ -202,7 +202,7 @@ public class Terminal
}
}
public void write( String text )
public synchronized void write( String text )
{
int x = m_cursorX;
int y = m_cursorY;
@@ -215,7 +215,7 @@ public class Terminal
}
}
public void scroll( int yDiff )
public synchronized void scroll( int yDiff )
{
if( yDiff != 0 )
{
@@ -245,7 +245,7 @@ public class Terminal
}
}
public void clear()
public synchronized void clear()
{
for( int y = 0; y < m_height; ++y )
{
@@ -256,7 +256,7 @@ public class Terminal
m_changed = true;
}
public void clearLine()
public synchronized void clearLine()
{
int y = m_cursorY;
if( y >= 0 && y < m_height )
@@ -268,7 +268,7 @@ public class Terminal
}
}
public TextBuffer getLine( int y )
public synchronized TextBuffer getLine( int y )
{
if( y >= 0 && y < m_height )
{
@@ -277,7 +277,7 @@ public class Terminal
return null;
}
public void setLine( int y, String text, String textColour, String backgroundColour )
public synchronized void setLine( int y, String text, String textColour, String backgroundColour )
{
m_text[y].write( text );
m_textColour[y].write( textColour );
@@ -285,7 +285,7 @@ public class Terminal
m_changed = true;
}
public TextBuffer getTextColourLine( int y )
public synchronized TextBuffer getTextColourLine( int y )
{
if( y>=0 && y<m_height )
{
@@ -294,7 +294,7 @@ public class Terminal
return null;
}
public TextBuffer getBackgroundColourLine( int y )
public synchronized TextBuffer getBackgroundColourLine( int y )
{
if( y>=0 && y<m_height )
{
@@ -318,7 +318,7 @@ public class Terminal
m_changed = false;
}
public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
{
nbttagcompound.setInteger( "term_cursorX", m_cursorX );
nbttagcompound.setInteger( "term_cursorY", m_cursorY );
@@ -338,7 +338,7 @@ public class Terminal
return nbttagcompound;
}
public void readFromNBT( NBTTagCompound nbttagcompound )
public synchronized void readFromNBT( NBTTagCompound nbttagcompound )
{
m_cursorX = nbttagcompound.getInteger( "term_cursorX" );
m_cursorY = nbttagcompound.getInteger( "term_cursorY" );

View File

@@ -0,0 +1,117 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import gnu.trove.map.hash.TObjectLongHashMap;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
public class ComputerTracker
{
private final WeakReference<Computer> computer;
private final int computerId;
private long tasks;
private long totalTime;
private long maxTime;
private long serverCount;
private long serverTime;
private final TObjectLongHashMap<TrackingField> fields;
public ComputerTracker( Computer computer )
{
this.computer = new WeakReference<>( computer );
this.computerId = computer.getID();
this.fields = new TObjectLongHashMap<>();
}
ComputerTracker( ComputerTracker timings )
{
this.computer = timings.computer;
this.computerId = timings.computerId;
this.tasks = timings.tasks;
this.totalTime = timings.totalTime;
this.maxTime = timings.maxTime;
this.serverCount = timings.serverCount;
this.serverTime = timings.serverTime;
this.fields = new TObjectLongHashMap<>( timings.fields );
}
@Nullable
public Computer getComputer()
{
return computer.get();
}
public int getComputerId()
{
return computerId;
}
public long getTasks()
{
return tasks;
}
public long getTotalTime()
{
return totalTime;
}
public long getMaxTime()
{
return maxTime;
}
public long getAverage()
{
return totalTime / tasks;
}
void addTaskTiming( long time )
{
tasks++;
totalTime += time;
if( time > maxTime ) maxTime = time;
}
void addMainTiming( long time )
{
serverCount++;
serverTime += time;
}
void addValue( TrackingField field, long change )
{
synchronized( fields )
{
fields.adjustOrPutValue( field, change, change );
}
}
public long get( TrackingField field )
{
if( field == TrackingField.TASKS ) return tasks;
if( field == TrackingField.MAX_TIME ) return maxTime;
if( field == TrackingField.TOTAL_TIME ) return totalTime;
if( field == TrackingField.AVERAGE_TIME ) return tasks == 0 ? 0 : totalTime / tasks;
if( field == TrackingField.SERVER_COUNT ) return serverCount;
if( field == TrackingField.SERVER_TIME ) return serverTime;
synchronized( fields )
{
return fields.get( field );
}
}
public String getFormatted( TrackingField field )
{
return field.format( get( field ) );
}
}

View File

@@ -0,0 +1,49 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
public interface Tracker
{
@Deprecated
default void addTiming( Computer computer, long time )
{
}
/**
* Report how long a task executed on the computer thread took.
*
* Computer thread tasks include events or a computer being turned on/off.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addTaskTiming( Computer computer, long time )
{
//noinspection deprecation
addTiming( computer, time );
}
/**
* Report how long a task executed on the server thread took.
*
* Server tasks include actions performed by peripherals.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addServerTiming( Computer computer, long time )
{
}
/**
* Increment an arbitrary field by some value. Implementations may track how often this is called
* as well as the change, to compute some level of "average".
*
* @param computer The computer to increment
* @param field The field to increment.
* @param change The amount to increment said field by.
*/
default void addValue( Computer computer, TrackingField field, long change )
{
}
}

View File

@@ -0,0 +1,80 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class Tracking
{
static final AtomicInteger tracking = new AtomicInteger( 0 );
private static final Object lock = new Object();
private static final HashMap<UUID, TrackingContext> contexts = new HashMap<>();
private static final List<Tracker> trackers = new ArrayList<>();
public static TrackingContext getContext( UUID uuid )
{
synchronized( lock )
{
TrackingContext context = contexts.get( uuid );
if( context == null ) contexts.put( uuid, context = new TrackingContext() );
return context;
}
}
public static void add( Tracker tracker )
{
synchronized( lock )
{
trackers.add( tracker );
tracking.incrementAndGet();
}
}
public static void addTaskTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addTaskTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addTaskTiming( computer, time );
}
}
public static void addServerTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addServerTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addServerTiming( computer, time );
}
}
public static void addValue( Computer computer, TrackingField field, long change )
{
if( tracking.get() == 0 ) return;
synchronized( lock )
{
for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change );
for( Tracker tracker : trackers ) tracker.addValue( computer, field, change );
}
}
public static void reset()
{
synchronized( lock )
{
contexts.clear();
trackers.clear();
tracking.set( 0 );
}
}
}

View File

@@ -0,0 +1,111 @@
package dan200.computercraft.core.tracking;
import com.google.common.collect.MapMaker;
import dan200.computercraft.core.computer.Computer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Tracks timing information about computers, including how long they ran for
* and the number of events they handled.
*
* Note that this <em>will</em> track computers which have been deleted (hence
* the presence of {@link #timingLookup} and {@link #timings}
*/
public class TrackingContext implements Tracker
{
private boolean tracking = false;
private final List<ComputerTracker> timings = new ArrayList<>();
private final Map<Computer, ComputerTracker> timingLookup = new MapMaker().weakKeys().makeMap();
public synchronized void start()
{
if( !tracking ) Tracking.tracking.incrementAndGet();
tracking = true;
timings.clear();
timingLookup.clear();
}
public synchronized boolean stop()
{
if( !tracking ) return false;
Tracking.tracking.decrementAndGet();
tracking = false;
timingLookup.clear();
return true;
}
public synchronized List<ComputerTracker> getImmutableTimings()
{
ArrayList<ComputerTracker> timings = new ArrayList<>( this.timings.size() );
for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) );
return timings;
}
public synchronized List<ComputerTracker> getTimings()
{
return new ArrayList<>( timings );
}
@Override
public void addTaskTiming( Computer computer, long time )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addTaskTiming( time );
}
}
@Override
public void addServerTiming( Computer computer, long time )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addMainTiming( time );
}
}
@Override
public void addValue( Computer computer, TrackingField field, long change )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addValue( field, change );
}
}
}

View File

@@ -0,0 +1,88 @@
package dan200.computercraft.core.tracking;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.LongFunction;
public class TrackingField
{
private static final Map<String, TrackingField> fields = new HashMap<>();
public static final TrackingField TASKS = TrackingField.of( "tasks", "Tasks", x -> String.format( "%4d", x ) );
public static final TrackingField TOTAL_TIME = TrackingField.of( "total", "Total time", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", "Average time", x -> String.format( "%4.1fms", x / 1e6 ) );
public static final TrackingField MAX_TIME = TrackingField.of( "max", "Max time", x -> String.format( "%5.1fms", x / 1e6 ) );
public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", "Server task count", x -> String.format( "%4d", x ) );
public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", "Server task time", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", "Peripheral calls", TrackingField::formatDefault );
public static final TrackingField FS_OPS = TrackingField.of( "fs", "Filesystem operations", TrackingField::formatDefault );
public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", "Turtle operations", TrackingField::formatDefault );
public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", "HTTP requests", TrackingField::formatDefault );
public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", "HTTP upload", TrackingField::formatBytes );
public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", "HTTT download", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes );
private final String id;
private final String displayName;
private final LongFunction<String> format;
public String id()
{
return id;
}
public String displayName()
{
return displayName;
}
private TrackingField( String id, String displayName, LongFunction<String> format )
{
this.id = id;
this.displayName = displayName;
this.format = format;
}
public String format( long value )
{
return format.apply( value );
}
public static TrackingField of( String id, String displayName, LongFunction<String> format )
{
TrackingField field = new TrackingField( id, displayName, format );
fields.put( id, field );
return field;
}
public static Map<String, TrackingField> fields()
{
return Collections.unmodifiableMap( fields );
}
private static String formatDefault( long value )
{
return String.format( "%6d", value );
}
/**
* So technically a kibibyte, but let's not argue here.
*/
private static final int KILOBYTE_SIZE = 1024;
private static final String SI_PREFIXES = "KMGT";
private static String formatBytes( long bytes )
{
if( bytes < 1024 ) return String.format( "%10d B", bytes );
int exp = (int) (Math.log( bytes ) / Math.log( KILOBYTE_SIZE ));
if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length();
return String.format( "%10.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) );
}
}

View File

@@ -4,9 +4,13 @@ import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerTimeTracker;
import dan200.computercraft.core.tracking.ComputerTracker;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingContext;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.command.framework.*;
import dan200.computercraft.shared.computer.core.ServerComputer;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.Entity;
@@ -25,6 +29,12 @@ import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
public final class CommandComputerCraft extends CommandDelegate
{
public static final UUID SYSTEM_UUID = new UUID( 0, 0 );
private static final int DUMP_LIST_ID = 5373952;
private static final int DUMP_SINGLE_ID = 1844510720;
private static final int TRACK_ID = 373882880;
public CommandComputerCraft()
{
super( create() );
@@ -49,7 +59,7 @@ public final class CommandComputerCraft extends CommandDelegate
{
if( arguments.size() == 0 )
{
TextTable table = new TextTable( "Instance", "Id", "On", "Position" );
TextTable table = new TextTable( DUMP_LIST_ID, "Instance", "Id", "On", "Position" );
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
@@ -95,7 +105,7 @@ public final class CommandComputerCraft extends CommandDelegate
{
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
TextTable table = new TextTable();
TextTable table = new TextTable( DUMP_SINGLE_ID );
table.addRow( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
table.addRow( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
table.addRow( header( "Label" ), text( computer.getLabel() ) );
@@ -311,7 +321,7 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
ComputerTimeTracker.start();
getTimingContext( context ).start();
String stopCommand = "/" + context.parent().getFullPath() + " stop";
context.getSender().sendMessage( list(
@@ -330,23 +340,54 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
if( !ComputerTimeTracker.stop() ) throw new CommandException( "Tracking not enabled" );
displayTimings( context );
TrackingContext timings = getTimingContext( context );
if( !timings.stop() ) throw new CommandException( "Tracking not enabled" );
displayTimings( context, timings.getImmutableTimings(), TrackingField.AVERAGE_TIME );
}
} );
track.register( new SubCommandBase(
"dump", "Dump the latest track results", UserLevel.OWNER_OP,
"dump", "[kind]", "Dump the latest track results", UserLevel.OWNER_OP,
"Dump the latest results of computer tracking."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
displayTimings( context );
TrackingField field = TrackingField.AVERAGE_TIME;
if( arguments.size() >= 1 )
{
field = TrackingField.fields().get( arguments.get( 0 ) );
if( field == null ) throw new CommandException( "Unknown field '" + arguments.get( 0 ) + "'" );
}
displayTimings( context, getTimingContext( context ).getImmutableTimings(), field );
}
@Nonnull
@Override
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
if( arguments.size() == 1 )
{
String match = arguments.get( 0 );
List<String> out = new ArrayList<>();
for( String key : TrackingField.fields().keySet() )
{
if( CommandBase.doesStringStartWith( match, key ) ) out.add( key );
}
out.sort( Comparator.naturalOrder() );
return out;
}
else
{
return super.getCompletion( context, arguments );
}
}
} );
root.register( new SubCommandBase(
"reload", "Reload the ComputerCraft config file", UserLevel.OWNER_OP,
"Reload the ComputerCraft config file"
@@ -390,13 +431,22 @@ public final class CommandComputerCraft extends CommandDelegate
}
}
private static void displayTimings( CommandContext context ) throws CommandException
private static TrackingContext getTimingContext( CommandContext context )
{
List<ComputerTimeTracker.Timings> timings = ComputerTimeTracker.getTimings();
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
Entity entity = context.getSender().getCommandSenderEntity();
if( entity instanceof EntityPlayerMP )
{
return Tracking.getContext( entity.getUniqueID() );
}
else
{
return Tracking.getContext( SYSTEM_UUID );
}
}
timings.sort( Comparator.comparing( ComputerTimeTracker.Timings::getAverage ).reversed() );
TextTable table = new TextTable( "Computer", "Tasks", "Total", "Average", "Maximum" );
private static void displayTimings( CommandContext context, List<ComputerTracker> timings, TrackingField field ) throws CommandException
{
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
@@ -411,7 +461,17 @@ public final class CommandComputerCraft extends CommandDelegate
ICommandSender sender = context.getSender();
boolean isPlayer = sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
for( ComputerTimeTracker.Timings entry : timings )
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( field ) ).reversed() );
boolean defaultLayout = field == TrackingField.TASKS || field == TrackingField.TOTAL_TIME
|| field == TrackingField.AVERAGE_TIME || field == TrackingField.MAX_TIME;
TextTable table = defaultLayout
? new TextTable( TRACK_ID, "Computer", "Tasks", "Total", "Average", "Maximum" )
: new TextTable( TRACK_ID, "Computer", field.displayName() );
for( ComputerTracker entry : timings )
{
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
@@ -437,13 +497,20 @@ public final class CommandComputerCraft extends CommandDelegate
) );
}
table.addRow(
computerComponent,
formatted( "%4d", entry.getTasks() ),
text( String.format( "%7.1f", entry.getTotalTime() / 1e6 ) + "ms" ),
text( String.format( "%4.1f", entry.getAverage() / 1e6 ) + "ms" ),
text( String.format( "%5.1f", entry.getMaxTime() / 1e6 ) + "ms" )
);
if( defaultLayout )
{
table.addRow(
computerComponent,
text( entry.getFormatted( TrackingField.TASKS ) ),
text( entry.getFormatted( TrackingField.TOTAL_TIME ) ),
text( entry.getFormatted( TrackingField.AVERAGE_TIME ) ),
text( entry.getFormatted( TrackingField.MAX_TIME ) )
);
}
else
{
table.addRow( computerComponent, text( entry.getFormatted( field ) ) );
}
}
table.displayTo( context.getSender() );

View File

@@ -0,0 +1,150 @@
package dan200.computercraft.shared.command.framework;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
/**
* Adapated from Sponge's PaginationCalculator
*/
public class TextFormatter
{
private static final int SPACE_WIDTH = 4;
private static final char PADDING_CHAR = '\u02cc';
/**
* Yoinked from FontRenderer
*
* @see net.minecraft.client.gui.FontRenderer#charWidth
* @see net.minecraft.client.gui.FontRenderer#getCharWidth(char)
*/
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[]{
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final int[] EXTRA_CHARS = new int[]{
'\u20e2', '\u261b',
};
private static final byte[] EXTRA_WIDTHS = new byte[]{
8, 4,
};
private static int getWidth( int codePoint )
{
// Escape codes
if( codePoint == 167 ) return -1;
// Space and non-breaking space
if( codePoint == 32 || codePoint == 160 ) return 4;
// Built-in characters
int nonUnicodeIdx = CHARACTERS.indexOf( codePoint );
if( codePoint > 0 && nonUnicodeIdx != -1 ) return CHAR_WIDTHS[nonUnicodeIdx];
// Other special characters we use.
int extraIdx = Arrays.binarySearch( EXTRA_CHARS, codePoint );
if( extraIdx >= 0 ) return EXTRA_WIDTHS[extraIdx];
return 0;
}
private static int getWidth( ITextComponent component )
{
int total = 0;
if( component instanceof TextComponentString )
{
String contents = component.getUnformattedComponentText();
int bold = component.getStyle().getBold() ? 1 : 0;
for( int i = 0; i < contents.length(); i++ )
{
int cp = contents.charAt( i );
assert cp != '\n';
int width = getWidth( cp );
if( width < 0 )
{
i++;
}
else
{
total += width + bold;
}
}
}
for( ITextComponent child : component.getSiblings() )
{
total += getWidth( child );
}
return total;
}
public static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
public static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
public static int getWidthFor( ITextComponent component, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( component ) : component.getUnformattedText().length();
}
public static int getWidthFor( int codepoint, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( codepoint ) : 1;
}
public static void appendFixedWidth( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
out.appendSibling( entry );
int width = getWidthFor( entry, sender );
int delta = maxWidth - width;
if( delta > 0 )
{
int spaces = delta / SPACE_WIDTH;
int extra = delta % SPACE_WIDTH;
// Append a fixed number of spaces
if( spaces > 0 ) out.appendSibling( new TextComponentString( StringUtils.repeat( ' ', spaces ) ) );
// Append several minor characters to pad to a full string
if( extra > 0 )
{
out.appendSibling( coloured( StringUtils.repeat( PADDING_CHAR, extra ), TextFormatting.GRAY ) );
}
}
}
}

View File

@@ -1,113 +1,49 @@
package dan200.computercraft.shared.command.framework;
import com.google.common.collect.Lists;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.network.ComputerCraftPacket;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
import static dan200.computercraft.shared.command.framework.TextFormatter.*;
public class TextTable
{
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[]{
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final ITextComponent SEPARATOR = coloured( " | ", TextFormatting.GRAY );
private static final ITextComponent SEPARATOR = coloured( "| ", TextFormatting.GRAY );
private static final ITextComponent LINE = text( "\n" );
private static int getWidth( char character, ICommandSender sender )
{
if( sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer) )
{
// Use font widths here.
if( character == 167 )
{
return -1;
}
else if( character == 32 )
{
return 4;
}
else if( CHARACTERS.indexOf( character ) != -1 )
{
return CHAR_WIDTHS[character];
}
else
{
// Eh, close enough.
return 6;
}
}
else
{
return 1;
}
}
private static int getWidth( ITextComponent text, ICommandSender sender )
{
int sum = 0;
String chars = text.getUnformattedText();
for( int i = 0; i < chars.length(); i++ )
{
sum += getWidth( chars.charAt( i ), sender );
}
return sum;
}
private static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
private static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
private final int id;
private int columns = -1;
private final ITextComponent[] header;
private final List<ITextComponent[]> rows = Lists.newArrayList();
public TextTable( @Nonnull ITextComponent... header )
public TextTable( int id, @Nonnull ITextComponent... header )
{
this.id = id;
this.header = header;
this.columns = header.length;
}
public TextTable()
public TextTable(int id)
{
header = null;
this.id = id;
this.header = null;
}
public TextTable( @Nonnull String... header )
public TextTable( int id, @Nonnull String... header )
{
this.id = id;
this.header = new ITextComponent[header.length];
for( int i = 0; i < header.length; i++ )
{
@@ -134,128 +70,100 @@ public class TextTable
{
if( columns <= 0 ) return;
final int maxWidth = getMaxWidth( sender );
int[] minWidths = new int[columns];
int[] maxWidths = new int[columns];
int[] rowWidths = new int[columns];
if( header != null )
{
for( int i = 0; i < columns; i++ )
{
maxWidths[i] = minWidths[i] = getWidth( header[i], sender );
maxWidths[i] = getWidthFor( header[i], sender );
}
}
// Limit the number of rows to something sensible.
int limit = isPlayer( sender ) ? 30 : 100;
if( limit > rows.size() ) limit = rows.size();
// Limit the number of rows to fit within a single chat window on default Minecraft
// options.
int height = isPlayer( sender ) ? 18 : 100;
int limit = rows.size() <= height ? rows.size() : height - 1;
for( int y = 0; y < limit; y++ )
{
ITextComponent[] row = rows.get( y );
for( int i = 0; i < row.length; i++ )
{
int width = getWidth( row[i], sender );
rowWidths[i] += width;
if( width > maxWidths[i] )
{
maxWidths[i] = width;
}
int width = getWidthFor( row[i], sender ) + 3;
if( width > maxWidths[i] ) maxWidths[i] = width;
}
}
// Calculate the average width
for( int i = 0; i < columns; i++ )
{
rowWidths[i] = Math.max( rowWidths[i], rows.size() );
}
// Add a small amount of extra padding. We include this here instead of the separator to allow
// for "extra" characters
for( int i = 0; i < maxWidths.length; i++ ) maxWidths[i] += 4;
int totalWidth = (columns - 1) * getWidth( SEPARATOR, sender );
int totalWidth = (columns - 1) * getWidthFor( SEPARATOR, sender );
for( int x : maxWidths ) totalWidth += x;
// TODO: Limit the widths of some entries if totalWidth > maxWidth
ITextComponent out = new TextComponentString( "" );
List<ITextComponent> out = new ArrayList<>();
if( header != null )
{
for( int i = 0; i < columns; i++ )
TextComponentString line = new TextComponentString( "" );
for( int i = 0; i < columns - 1; i++ )
{
if( i != 0 ) out.appendSibling( SEPARATOR );
appendFixed( out, sender, header[i], maxWidths[i] );
appendFixedWidth( line, sender, header[i], maxWidths[i] );
line.appendSibling( SEPARATOR );
}
out.appendSibling( LINE );
line.appendSibling( header[columns - 1] );
out.add( line );
// Round the width up rather than down
int rowCharWidth = getWidth( '=', sender );
int rowCharWidth = getWidthFor( '=', sender );
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
out.appendSibling( LINE );
out.add( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
}
for( int i = 0; i < limit; i++ )
{
TextComponentString line = new TextComponentString( "" );
ITextComponent[] row = rows.get( i );
if( i != 0 ) out.appendSibling( LINE );
for( int j = 0; j < columns; j++ )
for( int j = 0; j < columns - 1; j++ )
{
if( j != 0 ) out.appendSibling( SEPARATOR );
appendFixed( out, sender, row[j], maxWidths[j] );
appendFixedWidth( line, sender, row[j], maxWidths[j] );
line.appendSibling( SEPARATOR );
}
line.appendSibling( row[columns - 1] );
out.add( line );
}
if( limit != rows.size() )
if( rows.size() > limit )
{
out.appendSibling( LINE );
out.appendSibling( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) );
out.add( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) );
}
sender.sendMessage( out );
}
private static void appendFixed( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
int length = getWidth( entry, sender );
int delta = length - maxWidth;
if( delta < 0 )
if( isPlayer( sender ) && id != 0 )
{
// Convert to overflow;
delta = -delta;
ComputerCraftPacket packet = new ComputerCraftPacket();
packet.m_packetType = ComputerCraftPacket.PostChat;
packet.m_dataInt = new int[]{ id };
// We have to remove some padding as there is a padding added between formatted and unformatted text
if( !entry.getStyle().isEmpty() && isPlayer( sender ) ) delta -= 1;
out.appendSibling( entry );
int spaceWidth = getWidth( ' ', sender );
int spaces = delta / spaceWidth;
int missing = delta % spaceWidth;
spaces -= missing;
ITextComponent component = new TextComponentString( StringUtils.repeat( ' ', spaces < 0 ? 0 : spaces ) );
if( missing > 0 )
String[] lines = packet.m_dataString = new String[out.size()];
for( int i = 0; i < out.size(); i++ )
{
ITextComponent bold = new TextComponentString( StringUtils.repeat( ' ', missing ) );
bold.getStyle().setBold( true );
component.appendSibling( bold );
lines[i] = ITextComponent.Serializer.componentToJson( out.get( i ) );
}
out.appendSibling( component );
}
else if( delta > 0 )
{
out.appendSibling( entry );
ComputerCraft.sendToPlayer( (EntityPlayerMP) sender, packet );
}
else
{
out.appendSibling( entry );
// We have to add some padding as we expect a padding between formatted and unformatted text
// and there won't be.
if( entry.getStyle().isEmpty() && isPlayer( sender ) ) out.appendText( " " );
ITextComponent result = new TextComponentString( "" );
for( int i = 0; i < out.size(); i++ )
{
if( i > 0 ) result.appendSibling( LINE );
result.appendSibling( out.get( 0 ) );
}
sender.sendMessage( result );
}
}
}

View File

@@ -36,7 +36,7 @@ public abstract class BlockGeneric extends Block implements
protected BlockGeneric( Material material )
{
super( material );
this.isBlockContainer = true;
this.hasTileEntity = true;
}
protected abstract IBlockState getDefaultBlockState( int damage, EnumFacing placedSide );

View File

@@ -86,7 +86,7 @@ public class ColourableRecipe extends IForgeRegistryEntry.Impl<IRecipe> implemen
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

View File

@@ -16,4 +16,5 @@ public interface IComputerItem
int getComputerID( @Nonnull ItemStack stack );
String getLabel( @Nonnull ItemStack stack );
ComputerFamily getFamily( @Nonnull ItemStack stack );
ItemStack withFamily(@Nonnull ItemStack stack, @Nonnull ComputerFamily family);
}

View File

@@ -27,7 +27,7 @@ import javax.annotation.Nullable;
public class ItemComputer extends ItemComputerBase
{
public static int HIGHEST_DAMAGE_VALUE_ID = 16382;
public ItemComputer( Block block )
{
super( block );
@@ -87,7 +87,7 @@ public class ItemComputer extends ItemComputerBase
TileEntity tile = world.getTileEntity( pos );
if( tile != null && tile instanceof IComputerTile )
{
IComputerTile computer = (IComputerTile)tile;
IComputerTile computer = (IComputerTile) tile;
setupComputerAfterPlacement( stack, computer );
}
return true;
@@ -146,10 +146,16 @@ public class ItemComputer extends ItemComputerBase
else
{
int damage = stack.getItemDamage() & 0x3fff;
return ( damage - 1 );
return (damage - 1);
}
}
@Override
public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family )
{
return ComputerItemFactory.create( getComputerID( stack ), getLabel( stack ), family );
}
@Override
public ComputerFamily getFamily( int damage )
{

View File

@@ -0,0 +1,66 @@
package dan200.computercraft.shared.computer.recipe;
import dan200.computercraft.shared.computer.items.IComputerItem;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapedRecipes;
import net.minecraft.world.World;
import net.minecraftforge.common.crafting.CraftingHelper;
import javax.annotation.Nonnull;
/**
* Represents a recipe which converts a computer from one form into another.
*/
public abstract class ComputerConvertRecipe extends ShapedRecipes
{
public ComputerConvertRecipe( String group, @Nonnull CraftingHelper.ShapedPrimer primer, @Nonnull ItemStack result )
{
super( group, primer.width, primer.height, primer.input, result );
}
@Nonnull
protected abstract ItemStack convert( @Nonnull ItemStack stack );
@Override
public boolean matches( @Nonnull InventoryCrafting inventory, @Nonnull World world )
{
// See if we match the recipe, and extract the input computercraft ID
ItemStack computerStack = null;
for( int y = 0; y < 3; ++y )
{
for( int x = 0; x < 3; ++x )
{
ItemStack stack = inventory.getStackInRowAndColumn( x, y );
Ingredient target = getIngredients().get( x + y * 3 );
// First verify we match the ingredient
if( !target.apply( stack ) ) return false;
// We want to ensure we have a computer item somewhere in the recipe
if( stack.getItem() instanceof IComputerItem ) computerStack = stack;
}
}
return computerStack != null;
}
@Nonnull
@Override
public ItemStack getCraftingResult( @Nonnull InventoryCrafting inventory )
{
for( int y = 0; y < 3; ++y )
{
for( int x = 0; x < 3; ++x )
{
ItemStack item = inventory.getStackInRowAndColumn( x, y );
// If we're a computer, convert!
if( item.getItem() instanceof IComputerItem ) return convert( item );
}
}
return ItemStack.EMPTY;
}
}

View File

@@ -0,0 +1,47 @@
package dan200.computercraft.shared.computer.recipe;
import com.google.gson.JsonObject;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.util.RecipeUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.util.JsonUtils;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.IRecipeFactory;
import net.minecraftforge.common.crafting.JsonContext;
import javax.annotation.Nonnull;
public class ComputerFamilyRecipe extends ComputerConvertRecipe
{
private final ComputerFamily family;
public ComputerFamilyRecipe( String group, @Nonnull CraftingHelper.ShapedPrimer primer, @Nonnull ItemStack result, ComputerFamily family )
{
super( group, primer, result );
this.family = family;
}
@Nonnull
@Override
protected ItemStack convert( @Nonnull ItemStack stack )
{
return ((IComputerItem) stack.getItem()).withFamily( stack, family );
}
public static class Factory implements IRecipeFactory
{
@Override
public IRecipe parse( JsonContext context, JsonObject json )
{
String group = JsonUtils.getString( json, "group", "" );
ComputerFamily family = RecipeUtil.getFamily( json, "family" );
CraftingHelper.ShapedPrimer primer = RecipeUtil.getPrimer( context, json );
ItemStack result = deserializeItem( JsonUtils.getJsonObject( json, "result" ), false );
return new ComputerFamilyRecipe( group, primer, result, family );
}
}
}

View File

@@ -0,0 +1,66 @@
package dan200.computercraft.shared.computer.recipe;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.util.RecipeUtil;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.crafting.IIngredientFactory;
import net.minecraftforge.common.crafting.JsonContext;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents an ingredient which requires an item to have a specific
* computer family. This allows us to have operations which only work
* on normal or
*/
public class ComputerIngredient extends Ingredient
{
private final IComputerItem item;
private final ComputerFamily family;
public <T extends Item & IComputerItem> ComputerIngredient( T item, int data, ComputerFamily family )
{
super( new ItemStack( item, 1, data ) );
this.item = item;
this.family = family;
}
@Override
public boolean apply( @Nullable ItemStack stack )
{
return stack != null && stack.getItem() == item && item.getFamily( stack ) == family;
}
public static class Factory implements IIngredientFactory
{
@Nonnull
@Override
public Ingredient parse( JsonContext context, JsonObject json )
{
String itemName = context.appendModId( JsonUtils.getString( json, "item" ) );
int data = JsonUtils.getInt( json, "data", 0 );
ComputerFamily family = RecipeUtil.getFamily( json, "family" );
Item item = ForgeRegistries.ITEMS.getValue( new ResourceLocation( itemName ) );
if( item == null ) throw new JsonSyntaxException( "Unknown item '" + itemName + "'" );
if( !(item instanceof IComputerItem) )
{
throw new JsonSyntaxException( "Item '" + itemName + "' is not a computer item" );
}
return new ComputerIngredient( (Item & IComputerItem) item, data, family );
}
}
}

View File

@@ -54,15 +54,7 @@ public class ItemDiskLegacy extends Item
@Nonnull
public static ItemStack createFromIDAndColour( int id, String label, int colour )
{
if( colour != Colour.Blue.getHex() )
{
return ItemDiskExpanded.createFromIDAndColour( id, label, colour );
}
ItemStack stack = new ItemStack( ComputerCraft.Items.disk, 1 );
ComputerCraft.Items.disk.setDiskID( stack, id );
ComputerCraft.Items.disk.setLabel( stack, label );
return stack;
return ItemDiskExpanded.createFromIDAndColour( id, label, colour );
}
public int getDiskID( @Nonnull ItemStack stack )

View File

@@ -91,7 +91,7 @@ public class DiskRecipe extends IForgeRegistryEntry.Impl<IRecipe> implements IRe
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

View File

@@ -33,7 +33,7 @@ public class PrintoutRecipe extends IForgeRegistryEntry.Impl<IRecipe> implements
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

View File

@@ -33,6 +33,7 @@ public class ComputerCraftPacket
public static final byte ComputerTerminalChanged = 8;
public static final byte ComputerDeleted = 9;
public static final byte PlayRecord = 10;
public static final byte PostChat = 11;
// Packet class
public byte m_packetType;

View File

@@ -332,7 +332,8 @@ public class BlockCable extends BlockPeripheralBase
item = PeripheralItemFactory.create( PeripheralType.Cable, null, 1 );
}
cable.networkChanged();
cable.modemChanged();
cable.connectionsChanged();
if( !world.isRemote && !player.capabilities.isCreativeMode ) dropItem( world, pos, item );
return false;
@@ -382,7 +383,7 @@ public class BlockCable extends BlockPeripheralBase
TileCable cable = (TileCable) tile;
if( cable.getPeripheralType() != PeripheralType.WiredModem )
{
cable.networkChanged();
cable.connectionsChanged();
}
}

View File

@@ -98,8 +98,8 @@ public class ItemCable extends ItemPeripheralBase
TileEntity tile = world.getTileEntity( pos );
if( tile != null && tile instanceof TileCable )
{
TileCable cable = (TileCable)tile;
cable.networkChanged();
TileCable cable = (TileCable) tile;
cable.connectionsChanged();
}
return EnumActionResult.SUCCESS;
}
@@ -131,7 +131,8 @@ public class ItemCable extends ItemPeripheralBase
if( tile != null && tile instanceof TileCable )
{
TileCable cable = (TileCable)tile;
cable.networkChanged();
cable.modemChanged();
cable.connectionsChanged();
}
return EnumActionResult.SUCCESS;
}
@@ -153,7 +154,8 @@ public class ItemCable extends ItemPeripheralBase
if( tile != null && tile instanceof TileCable )
{
TileCable cable = (TileCable)tile;
cable.networkChanged();
cable.modemChanged();
cable.connectionsChanged();
}
return EnumActionResult.SUCCESS;
}

View File

@@ -62,9 +62,9 @@ public class DiskDrivePeripheral implements IPeripheral
{
case 0:
{
// isPresent
// isDiskPresent
return new Object[] {
m_diskDrive.getDiskStack() != null
!m_diskDrive.getDiskStack().isEmpty()
};
}
case 1:

View File

@@ -422,6 +422,7 @@ public class TileDiskDrive extends TilePeripheralBase
return new DiskDrivePeripheral( this );
}
@Nonnull
public ItemStack getDiskStack()
{
synchronized( this )

View File

@@ -16,10 +16,7 @@ import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.BlockCableModemVariant;
import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.PeripheralUtil;
import dan200.computercraft.api.network.wired.IWiredElementTile;
import net.minecraft.block.Block;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
@@ -31,15 +28,15 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class TileCable extends TileModemBase implements IWiredElementTile
public class TileCable extends TileModemBase
{
public static final double MIN = 0.375;
public static final double MAX = 1 - MIN;
@@ -75,26 +72,13 @@ public class TileCable extends TileModemBase implements IWiredElementTile
public Vec3d getPosition()
{
BlockPos pos = m_entity.getPos();
return new Vec3d( (double) pos.getX() + 0.5, (double) pos.getY() + 0.5, (double) pos.getZ() + 0.5 );
}
@Nonnull
@Override
public Map<String, IPeripheral> getPeripherals()
{
IPeripheral peripheral = m_entity.getConnectedPeripheral();
return peripheral != null
? Collections.singletonMap( m_entity.getConnectedPeripheralName(), peripheral )
: Collections.emptyMap();
return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
}
@Override
protected void attachPeripheral( String name, IPeripheral peripheral )
{
if( !name.equals( m_entity.getConnectedPeripheralName() ) )
{
((WiredModemPeripheral) m_entity.m_modem).attachPeripheral( name, peripheral );
}
((WiredModemPeripheral) m_entity.m_modem).attachPeripheral( name, peripheral );
}
@Override
@@ -107,9 +91,9 @@ public class TileCable extends TileModemBase implements IWiredElementTile
// Members
private boolean m_peripheralAccessAllowed;
private int m_attachedPeripheralID;
private WiredModemLocalPeripheral m_peripheral = new WiredModemLocalPeripheral();
private boolean m_destroyed;
private boolean m_destroyed = false;
private boolean m_hasDirection = false;
private boolean m_connectionsFormed = false;
@@ -117,14 +101,6 @@ public class TileCable extends TileModemBase implements IWiredElementTile
private WiredModemElement m_cable;
private IWiredNode m_node;
public TileCable()
{
m_peripheralAccessAllowed = false;
m_attachedPeripheralID = -1;
m_destroyed = false;
}
@Override
protected ModemPeripheral createPeripheral()
{
@@ -132,6 +108,12 @@ public class TileCable extends TileModemBase implements IWiredElementTile
m_node = m_cable.getNode();
return new WiredModemPeripheral( m_cable )
{
@Override
protected boolean canSeePeripheral( @Nonnull String peripheralName )
{
return !peripheralName.equals( m_peripheral.getConnectedName() );
}
@Nonnull
@Override
public Vec3d getPosition()
@@ -287,12 +269,32 @@ public class TileCable extends TileModemBase implements IWiredElementTile
((BlockGeneric) getBlockType()).dropItem( getWorld(), getPos(), PeripheralItemFactory.create( PeripheralType.WiredModem, getLabel(), 1 ) );
setLabel( null );
setBlockState( getBlockState().withProperty( BlockCable.Properties.MODEM, BlockCableModemVariant.None ) );
networkChanged();
modemChanged();
connectionsChanged();
break;
return;
}
}
}
if( !world.isRemote && m_peripheralAccessAllowed )
{
if( m_peripheral.attach( world, getPos(), dir ) ) updateConnectedPeripherals();
}
}
@Override
public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour )
{
super.onNeighbourTileEntityChange( neighbour );
if( !world.isRemote && m_peripheralAccessAllowed )
{
EnumFacing facing = getDirection();
if( getPos().offset( facing ).equals( neighbour ) )
{
if( m_peripheral.attach( world, getPos(), facing ) ) updateConnectedPeripherals();
}
}
}
public AxisAlignedBB getModemBounds()
@@ -395,9 +397,9 @@ public class TileCable extends TileModemBase implements IWiredElementTile
if( !getWorld().isRemote )
{
// On server, we interacted if a peripheral was found
String oldPeriphName = getConnectedPeripheralName();
String oldPeriphName = m_peripheral.getConnectedName();
togglePeripheralAccess();
String periphName = getConnectedPeripheralName();
String periphName = m_peripheral.getConnectedName();
if( !Objects.equal( periphName, oldPeriphName ) )
{
@@ -432,7 +434,7 @@ public class TileCable extends TileModemBase implements IWiredElementTile
// Read properties
super.readFromNBT( nbttagcompound );
m_peripheralAccessAllowed = nbttagcompound.getBoolean( "peripheralAccess" );
m_attachedPeripheralID = nbttagcompound.getInteger( "peripheralID" );
m_peripheral.readNBT( nbttagcompound, "" );
}
@Nonnull
@@ -442,7 +444,7 @@ public class TileCable extends TileModemBase implements IWiredElementTile
// Write properties
nbttagcompound = super.writeToNBT( nbttagcompound );
nbttagcompound.setBoolean( "peripheralAccess", m_peripheralAccessAllowed );
nbttagcompound.setInteger( "peripheralID", m_attachedPeripheralID );
m_peripheral.writeNBT( nbttagcompound, "" );
return nbttagcompound;
}
@@ -470,27 +472,31 @@ public class TileCable extends TileModemBase implements IWiredElementTile
{
if( !m_connectionsFormed )
{
networkChanged();
if( m_peripheralAccessAllowed ) m_node.invalidate();
m_connectionsFormed = true;
connectionsChanged();
if( m_peripheralAccessAllowed )
{
m_peripheral.attach( world, pos, m_dir );
updateConnectedPeripherals();
}
}
}
}
public void networkChanged()
public void connectionsChanged()
{
if( getWorld().isRemote ) return;
if( modemChanged() ) m_node.invalidate();
IBlockState state = getBlockState();
World world = getWorld();
BlockPos current = getPos();
for( EnumFacing facing : EnumFacing.VALUES )
{
if( !world.isBlockLoaded( pos ) ) continue;
IWiredElement element = ComputerCraft.getWiredElementAt( world, current.offset( facing ), facing.getOpposite() );
BlockPos offset = current.offset( facing );
if( !world.isBlockLoaded( offset ) ) continue;
IWiredElement element = ComputerCraft.getWiredElementAt( world, offset, facing.getOpposite() );
if( element == null ) continue;
if( BlockCable.canConnectIn( state, facing ) )
@@ -506,27 +512,19 @@ public class TileCable extends TileModemBase implements IWiredElementTile
}
}
private boolean modemChanged()
public void modemChanged()
{
if( getWorld().isRemote ) return false;
boolean requiresUpdate = false;
if( getWorld().isRemote ) return;
PeripheralType type = getPeripheralType();
if( type == PeripheralType.Cable )
{
m_attachedPeripheralID = -1;
}
if( type != PeripheralType.WiredModemWithCable && m_peripheralAccessAllowed )
{
m_peripheralAccessAllowed = false;
requiresUpdate = true;
m_peripheral.detach();
m_node.updatePeripherals( Collections.emptyMap() );
markDirty();
updateAnim();
}
return requiresUpdate;
}
// private stuff
@@ -534,61 +532,34 @@ public class TileCable extends TileModemBase implements IWiredElementTile
{
if( !m_peripheralAccessAllowed )
{
m_peripheral.attach( world, getPos(), getDirection() );
if( !m_peripheral.hasPeripheral() ) return;
m_peripheralAccessAllowed = true;
if( getConnectedPeripheral() == null )
{
m_peripheralAccessAllowed = false;
return;
}
m_node.updatePeripherals( m_peripheral.toMap() );
}
else
{
m_peripheral.detach();
m_peripheralAccessAllowed = false;
m_node.updatePeripherals( Collections.emptyMap() );
}
updateAnim();
m_node.invalidate();
}
private String getConnectedPeripheralName()
private void updateConnectedPeripherals()
{
IPeripheral periph = getConnectedPeripheral();
if( periph != null )
Map<String, IPeripheral> peripherals = m_peripheral.toMap();
if( peripherals.isEmpty() )
{
String type = periph.getType();
if( m_attachedPeripheralID < 0 )
{
m_attachedPeripheralID = IDAssigner.getNextIDFromFile( new File(
ComputerCraft.getWorldDir( getWorld() ),
"computer/lastid_" + type + ".txt"
) );
}
return type + "_" + m_attachedPeripheralID;
// If there are no peripherals then disable access and update the display state.
m_peripheralAccessAllowed = false;
updateAnim();
}
return null;
}
private IPeripheral getConnectedPeripheral()
{
if( m_peripheralAccessAllowed )
{
if( getPeripheralType() == PeripheralType.WiredModemWithCable )
{
EnumFacing facing = getDirection();
BlockPos neighbour = getPos().offset( facing );
IPeripheral peripheral = getPeripheral( getWorld(), neighbour, facing.getOpposite() );
return peripheral == null || peripheral instanceof WiredModemPeripheral ? null : peripheral;
}
}
return null;
}
public static IPeripheral getPeripheral( World world, BlockPos pos, EnumFacing facing )
{
Block block = world.getBlockState( pos ).getBlock();
if( block == ComputerCraft.Blocks.wiredModemFull || block == ComputerCraft.Blocks.cable ) return null;
return PeripheralUtil.getPeripheral( world, pos, facing );
m_node.updatePeripherals( peripherals );
}
@Override
@@ -597,13 +568,25 @@ public class TileCable extends TileModemBase implements IWiredElementTile
return true;
}
// IWiredElement tile
// IWiredElement capability
@Override
public boolean hasCapability( @Nonnull Capability<?> capability, @Nullable EnumFacing facing )
{
if( capability == CapabilityWiredElement.CAPABILITY ) return BlockCable.canConnectIn( getBlockState(), facing );
return super.hasCapability( capability, facing );
}
@Nullable
@Override
public IWiredElement getWiredElement( @Nonnull EnumFacing side )
public <T> T getCapability( @Nonnull Capability<T> capability, @Nullable EnumFacing facing )
{
return BlockCable.canConnectIn( getBlockState(), side ) ? m_cable : null;
if( capability == CapabilityWiredElement.CAPABILITY )
{
return BlockCable.canConnectIn( getBlockState(), facing ) ? CapabilityWiredElement.CAPABILITY.cast( m_cable ) : null;
}
return super.getCapability( capability, facing );
}
// IPeripheralTile

View File

@@ -9,12 +9,11 @@ package dan200.computercraft.shared.peripheral.modem;
import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredElementTile;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.TilePeripheralBase;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
@@ -23,13 +22,13 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.capabilities.Capability;
import javax.annotation.Nonnull;
import java.io.File;
import javax.annotation.Nullable;
import java.util.*;
public class TileWiredModemFull extends TilePeripheralBase implements IWiredElementTile
public class TileWiredModemFull extends TilePeripheralBase
{
private static class FullElement extends WiredModemElement
{
@@ -46,10 +45,7 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
for( int i = 0; i < 6; i++ )
{
WiredModemPeripheral modem = m_entity.m_modems[i];
if( modem != null && !name.equals( m_entity.getCachedPeripheralName( EnumFacing.VALUES[i] ) ) )
{
modem.attachPeripheral( name, peripheral );
}
if( modem != null ) modem.attachPeripheral( name, peripheral );
}
}
@@ -77,37 +73,29 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
BlockPos pos = m_entity.getPos();
return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
}
@Nonnull
@Override
public Map<String, IPeripheral> getPeripherals()
{
return m_entity.getPeripherals();
}
}
private WiredModemPeripheral[] m_modems = new WiredModemPeripheral[6];
private boolean m_peripheralAccessAllowed = false;
private int[] m_attachedPeripheralIDs = new int[6];
private String[] m_attachedPeripheralTypes = new String[6];
private WiredModemLocalPeripheral[] m_peripherals = new WiredModemLocalPeripheral[6];
private boolean m_destroyed = false;
private boolean m_connectionsFormed = false;
private final WiredModemElement m_element = new FullElement( this );
private final IWiredNode node = m_element.getNode();
private final IWiredNode m_node = m_element.getNode();
public TileWiredModemFull()
{
Arrays.fill( m_attachedPeripheralIDs, -1 );
for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i] = new WiredModemLocalPeripheral();
}
private void remove()
{
if( world == null || !world.isRemote )
{
node.remove();
m_node.remove();
m_connectionsFormed = false;
}
}
@@ -153,18 +141,29 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
{
if( !world.isRemote && m_peripheralAccessAllowed )
{
Map<String, IPeripheral> updated = getPeripherals();
if( updated.isEmpty() )
boolean hasChanged = false;
for( EnumFacing facing : EnumFacing.VALUES )
{
// If there are no peripherals then disable access and update the display state.
m_peripheralAccessAllowed = false;
updateAnim();
hasChanged |= m_peripherals[facing.ordinal()].attach( world, getPos(), facing );
}
// Always invalidate the node: it's more accurate than checking if the peripherals
// have changed
node.invalidate();
if( hasChanged ) updateConnectedPeripherals();
}
}
@Override
public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour )
{
if( !world.isRemote && m_peripheralAccessAllowed )
{
for( EnumFacing facing : EnumFacing.VALUES )
{
if( getPos().offset( facing ).equals( neighbour ) )
{
WiredModemLocalPeripheral peripheral = m_peripherals[facing.ordinal()];
if( peripheral.attach( world, getPos(), facing ) ) updateConnectedPeripherals();
}
}
}
}
@@ -181,9 +180,9 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
if( !getWorld().isRemote )
{
// On server, we interacted if a peripheral was found
Set<String> oldPeriphName = getPeripherals().keySet();
Set<String> oldPeriphName = getConnectedPeripheralNames();
togglePeripheralAccess();
Set<String> periphName = getPeripherals().keySet();
Set<String> periphName = getConnectedPeripheralNames();
if( !Objects.equal( periphName, oldPeriphName ) )
{
@@ -221,17 +220,7 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
{
super.readFromNBT( tag );
m_peripheralAccessAllowed = tag.getBoolean( "peripheralAccess" );
for( int i = 0; i < m_attachedPeripheralIDs.length; i++ )
{
if( tag.hasKey( "peripheralID_" + i, Constants.NBT.TAG_ANY_NUMERIC ) )
{
m_attachedPeripheralIDs[i] = tag.getInteger( "peripheralID_" + i );
}
if( tag.hasKey( "peripheralType_" + i, Constants.NBT.TAG_STRING ) )
{
m_attachedPeripheralTypes[i] = tag.getString( "peripheralType_" + i );
}
}
for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i].readNBT( tag, "_" + i );
}
@Nonnull
@@ -240,17 +229,7 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
{
tag = super.writeToNBT( tag );
tag.setBoolean( "peripheralAccess", m_peripheralAccessAllowed );
for( int i = 0; i < m_attachedPeripheralIDs.length; i++ )
{
if( m_attachedPeripheralIDs[i] >= 0 )
{
tag.setInteger( "peripheralID_" + i, m_attachedPeripheralIDs[i] );
}
if( m_attachedPeripheralTypes[i] != null )
{
tag.setString( "peripheralType_" + i, m_attachedPeripheralTypes[i] );
}
}
for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i].writeNBT( tag, "_" + i );
return tag;
}
@@ -294,15 +273,24 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
if( !m_connectionsFormed )
{
networkChanged();
m_connectionsFormed = true;
connectionsChanged();
if( m_peripheralAccessAllowed )
{
for( EnumFacing facing : EnumFacing.VALUES )
{
m_peripherals[facing.ordinal()].attach( world, getPos(), facing );
}
updateConnectedPeripherals();
}
}
}
super.update();
}
private void networkChanged()
private void connectionsChanged()
{
if( getWorld().isRemote ) return;
@@ -310,16 +298,15 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
BlockPos current = getPos();
for( EnumFacing facing : EnumFacing.VALUES )
{
if( !world.isBlockLoaded( pos ) ) continue;
BlockPos offset = current.offset( facing );
if( !world.isBlockLoaded( offset ) ) continue;
IWiredElement element = ComputerCraft.getWiredElementAt( world, current.offset( facing ), facing.getOpposite() );
IWiredElement element = ComputerCraft.getWiredElementAt( world, offset, facing.getOpposite() );
if( element == null ) continue;
// If we can connect to it then do so
node.connectTo( element.getNode() );
m_node.connectTo( element.getNode() );
}
node.invalidate();
}
// private stuff
@@ -327,69 +314,83 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
{
if( !m_peripheralAccessAllowed )
{
m_peripheralAccessAllowed = true;
if( getPeripherals().isEmpty() )
boolean hasAny = false;
for( EnumFacing facing : EnumFacing.VALUES )
{
m_peripheralAccessAllowed = false;
return;
WiredModemLocalPeripheral peripheral = m_peripherals[facing.ordinal()];
peripheral.attach( world, getPos(), facing );
hasAny |= peripheral.hasPeripheral();
}
if( !hasAny ) return;
m_peripheralAccessAllowed = true;
m_node.updatePeripherals( getConnectedPeripherals() );
}
else
{
m_peripheralAccessAllowed = false;
for( WiredModemLocalPeripheral peripheral : m_peripherals ) peripheral.detach();
m_node.updatePeripherals( Collections.emptyMap() );
}
updateAnim();
node.invalidate();
}
@Nonnull
private Map<String, IPeripheral> getPeripherals()
private Set<String> getConnectedPeripheralNames()
{
if( !m_peripheralAccessAllowed ) return Collections.emptySet();
Set<String> peripherals = new HashSet<>( 6 );
for( WiredModemLocalPeripheral m_peripheral : m_peripherals )
{
String name = m_peripheral.getConnectedName();
if( name != null ) peripherals.add( name );
}
return peripherals;
}
private Map<String, IPeripheral> getConnectedPeripherals()
{
if( !m_peripheralAccessAllowed ) return Collections.emptyMap();
Map<String, IPeripheral> peripherals = new HashMap<>( 6 );
for( EnumFacing facing : EnumFacing.VALUES )
{
BlockPos neighbour = getPos().offset( facing );
IPeripheral peripheral = TileCable.getPeripheral( getWorld(), neighbour, facing.getOpposite() );
if( peripheral != null && !(peripheral instanceof WiredModemPeripheral) )
{
String type = peripheral.getType();
int id = m_attachedPeripheralIDs[facing.ordinal()];
String oldType = m_attachedPeripheralTypes[facing.ordinal()];
if( id < 0 || !type.equals( oldType ) )
{
m_attachedPeripheralTypes[facing.ordinal()] = type;
id = m_attachedPeripheralIDs[facing.ordinal()] = IDAssigner.getNextIDFromFile( new File(
ComputerCraft.getWorldDir( getWorld() ),
"computer/lastid_" + type + ".txt"
) );
}
peripherals.put( type + "_" + id, peripheral );
}
}
for( WiredModemLocalPeripheral m_peripheral : m_peripherals ) m_peripheral.extendMap( peripherals );
return peripherals;
}
private String getCachedPeripheralName( EnumFacing facing )
private void updateConnectedPeripherals()
{
if( !m_peripheralAccessAllowed ) return null;
Map<String, IPeripheral> peripherals = getConnectedPeripherals();
if( peripherals.isEmpty() )
{
// If there are no peripherals then disable access and update the display state.
m_peripheralAccessAllowed = false;
updateAnim();
}
int id = m_attachedPeripheralIDs[facing.ordinal()];
String type = m_attachedPeripheralTypes[facing.ordinal()];
return id < 0 || type == null ? null : type + "_" + id;
m_node.updatePeripherals( peripherals );
}
// IWiredElementTile
@Nonnull
@Override
public IWiredElement getWiredElement( @Nonnull EnumFacing side )
public boolean hasCapability( @Nonnull Capability<?> capability, @Nullable EnumFacing facing )
{
return m_element;
return capability == CapabilityWiredElement.CAPABILITY || super.hasCapability( capability, facing );
}
@Nullable
@Override
public <T> T getCapability( @Nonnull Capability<T> capability, @Nullable EnumFacing facing )
{
if( capability == CapabilityWiredElement.CAPABILITY )
{
return CapabilityWiredElement.CAPABILITY.cast( m_element );
}
return super.getCapability( capability, facing );
}
// IPeripheralTile
@@ -402,6 +403,12 @@ public class TileWiredModemFull extends TilePeripheralBase implements IWiredElem
{
peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_element )
{
@Override
protected boolean canSeePeripheral( @Nonnull String peripheralName )
{
return !peripheralName.equals( m_peripherals[side.ordinal()].getConnectedName() );
}
@Nonnull
@Override
public Vec3d getPosition()

View File

@@ -1,7 +1,7 @@
package dan200.computercraft.shared.peripheral.modem;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.wired.WiredNode;

View File

@@ -0,0 +1,139 @@
package dan200.computercraft.shared.peripheral.modem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.PeripheralUtil;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.Map;
/**
* Represents a local peripheral exposed on the wired network
*
* This is responsible for getting the peripheral in world, tracking id and type and determining whether
* it has changed.
*/
public final class WiredModemLocalPeripheral
{
private int id;
private String type;
private IPeripheral peripheral;
/**
* Attach a new peripheral from the world
*
* @param world The world to search in
* @param origin The position to search from
* @param direction The direction so search in
* @return Whether the peripheral changed.
*/
public boolean attach( @Nonnull World world, @Nonnull BlockPos origin, @Nonnull EnumFacing direction )
{
IPeripheral oldPeripheral = this.peripheral;
IPeripheral peripheral = this.peripheral = getPeripheralFrom( world, origin, direction );
if( peripheral == null )
{
return oldPeripheral != null;
}
else
{
String type = peripheral.getType();
int id = this.id;
if( id > 0 && this.type == null )
{
// If we had an ID but no type, then just set the type.
this.type = type;
}
else if( id < 0 || !type.equals( this.type ) )
{
this.type = type;
this.id = IDAssigner.getNextIDFromFile( new File(
ComputerCraft.getWorldDir( world ),
"computer/lastid_" + type + ".txt"
) );
}
return oldPeripheral == null || !oldPeripheral.equals( peripheral );
}
}
/**
* Detach the current peripheral
*
* @return Whether the peripheral changed
*/
public boolean detach()
{
if( peripheral == null ) return false;
peripheral = null;
return true;
}
@Nullable
public String getConnectedName()
{
return peripheral != null ? type + "_" + id : null;
}
@Nullable
public IPeripheral getPeripheral()
{
return peripheral;
}
public boolean hasPeripheral()
{
return peripheral != null;
}
public void extendMap( @Nonnull Map<String, IPeripheral> peripherals )
{
if( peripheral != null ) peripherals.put( type + "_" + id, peripheral );
}
public Map<String, IPeripheral> toMap()
{
return peripheral == null
? Collections.emptyMap()
: Collections.singletonMap( type + "_" + id, peripheral );
}
public void writeNBT( @Nonnull NBTTagCompound tag, @Nonnull String suffix )
{
if( id >= 0 ) tag.setInteger( "peripheralID" + suffix, id );
if( type != null ) tag.setString( "peripheralType" + suffix, type );
}
public void readNBT( @Nonnull NBTTagCompound tag, @Nonnull String suffix )
{
id = tag.hasKey( "peripheralID" + suffix, Constants.NBT.TAG_ANY_NUMERIC )
? tag.getInteger( "peripheralID" + suffix ) : -1;
type = tag.hasKey( "peripheralType" + suffix, Constants.NBT.TAG_STRING )
? tag.getString( "peripheralType" + suffix ) : null;
}
private static IPeripheral getPeripheralFrom( World world, BlockPos pos, EnumFacing direction )
{
BlockPos offset = pos.offset( direction );
Block block = world.getBlockState( offset ).getBlock();
if( block == ComputerCraft.Blocks.wiredModemFull || block == ComputerCraft.Blocks.cable ) return null;
IPeripheral peripheral = PeripheralUtil.getPeripheral( world, offset, direction.getOpposite() );
return peripheral instanceof WiredModemPeripheral ? null : peripheral;
}
}

View File

@@ -10,7 +10,6 @@ import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -20,7 +19,7 @@ import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
public class WiredModemPeripheral extends ModemPeripheral implements IWiredSender
public abstract class WiredModemPeripheral extends ModemPeripheral implements IWiredSender
{
private final WiredModemElement modem;
@@ -57,12 +56,7 @@ public class WiredModemPeripheral extends ModemPeripheral implements IWiredSende
return modem.getWorld();
}
@Nonnull
@Override
public Vec3d getPosition()
{
return modem.getPosition();
}
protected abstract boolean canSeePeripheral( @Nonnull String peripheralName );
//endregion
//region IPeripheral
@@ -223,7 +217,7 @@ public class WiredModemPeripheral extends ModemPeripheral implements IWiredSende
private void attachPeripheralImpl( String periphName, IPeripheral peripheral )
{
if( !peripheralWrappers.containsKey( periphName ) )
if( !peripheralWrappers.containsKey( periphName ) && canSeePeripheral( periphName ) )
{
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
peripheralWrappers.put( periphName, wrapper );

View File

@@ -1,17 +1,22 @@
package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.common.ClientTerminal;
import gnu.trove.set.hash.TIntHashSet;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.math.BlockPos;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ClientMonitor extends ClientTerminal
{
private static final TIntHashSet displayLists = new TIntHashSet();
private static final Set<ClientMonitor> allMonitors = new HashSet<>();
private final TileMonitor origin;
public long lastRenderFrame = -1;
public int renderDisplayList = -1;
public BlockPos lastRenderPos = null;
public int[] renderDisplayLists = null;
public ClientMonitor( boolean colour, TileMonitor origin )
{
@@ -24,11 +29,60 @@ public class ClientMonitor extends ClientTerminal
return origin;
}
public void createLists()
{
if( renderDisplayLists == null )
{
renderDisplayLists = new int[3];
for( int i = 0; i < renderDisplayLists.length; i++ )
{
renderDisplayLists[i] = GlStateManager.glGenLists( 1 );
}
synchronized( allMonitors )
{
allMonitors.add( this );
}
}
}
public void destroy()
{
if( renderDisplayList != -1 )
if( renderDisplayLists != null )
{
ComputerCraft.deleteDisplayLists( renderDisplayList, 3 );
synchronized( allMonitors )
{
allMonitors.remove( this );
}
for( int list : renderDisplayLists )
{
GlStateManager.glDeleteLists( list, 1 );
}
renderDisplayLists = null;
}
}
public static void destroyAll()
{
synchronized( allMonitors )
{
for( Iterator<ClientMonitor> iterator = allMonitors.iterator(); iterator.hasNext(); )
{
ClientMonitor monitor = iterator.next();
if( monitor.renderDisplayLists != null )
{
for( int list : monitor.renderDisplayLists )
{
GlStateManager.glDeleteLists( list, 1 );
}
monitor.renderDisplayLists = null;
}
iterator.remove();
}
}
}
}

View File

@@ -243,7 +243,7 @@ public class MonitorPeripheral implements IPeripheral
case 24:
{
// getTextScale
return new Object[] { monitor.getTextScale() };
return new Object[] { monitor.getTextScale() / 2.0 };
}
}
return null;

View File

@@ -371,6 +371,15 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
}
}
@Override
public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family )
{
return PocketComputerItemFactory.create(
getComputerID( stack ), getLabel( stack ), getColour( stack ),
family, getUpgrade( stack )
);
}
// IMedia
@Override

View File

@@ -34,7 +34,7 @@ public class PocketComputerUpgradeRecipe extends IForgeRegistryEntry.Impl<IRecip
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

View File

@@ -10,6 +10,8 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.shared.command.CommandComputer;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.command.ContainerViewComputer;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
@@ -49,8 +51,9 @@ import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.DefaultWiredProvider;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import net.minecraft.block.Block;
import net.minecraft.command.CommandHandler;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -63,6 +66,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
@@ -115,6 +119,14 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
registerForgeHandlers();
}
@Override
public void initServer( MinecraftServer server )
{
CommandHandler handler = (CommandHandler) server.getCommandManager();
handler.registerCommand( new CommandComputer() );
handler.registerCommand( new CommandComputerCraft() );
}
@Override
public abstract boolean isClient();
@@ -124,11 +136,6 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
@Override
public abstract long getRenderFrame();
@Override
public void deleteDisplayLists( int list, int range )
{
}
@Override
public abstract Object getFixedWidthFontRenderer();
@@ -159,7 +166,8 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
packet.m_dataInt = new int[] { pos.getX(), pos.getY(), pos.getZ() };
}
ComputerCraft.sendToAllPlayers( packet );
NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint( world.provider.getDimension(), pos.getX(), pos.getY(), pos.getZ(), 64 );
ComputerCraft.sendToAllAround( packet, point );
}
@Override
@@ -490,7 +498,7 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
ComputerCraftAPI.registerMediaProvider( new DefaultMediaProvider() );
// Register network providers
ComputerCraftAPI.registerWiredProvider( new DefaultWiredProvider() );
CapabilityWiredElement.register();
}
private void registerForgeHandlers()

View File

@@ -16,6 +16,7 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
@@ -28,11 +29,11 @@ public interface IComputerCraftProxy
{
void preInit();
void init();
void initServer( MinecraftServer server );
boolean isClient();
boolean getGlobalCursorBlink();
long getRenderFrame();
void deleteDisplayLists( int list, int range );
Object getFixedWidthFontRenderer();
String getRecordInfo( @Nonnull ItemStack item );

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.api.turtle.event.TurtleActionEvent;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.turtle.core.*;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -161,70 +162,83 @@ public class TurtleAPI implements ILuaAPI
case 0:
{
// forward
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Forward ) );
}
case 1:
{
// back
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Back ) );
}
case 2:
{
// up
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Up ) );
}
case 3:
{
// down
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Down ) );
}
case 4:
{
// turnLeft
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Left ) );
}
case 5:
{
// turnRight
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Right ) );
}
case 6:
{
// dig
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Forward, side ) );
}
case 7:
{
// digUp
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Up, side ) );
}
case 8:
{
// digDown
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Down, side ) );
}
case 9:
{
// place
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Forward, args ) );
}
case 10:
{
// placeUp
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Up, args ) );
}
case 11:
{
// placeDown
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Down, args ) );
}
case 12:
{
// drop
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Forward, count ) );
}
case 13:
@@ -294,48 +308,56 @@ public class TurtleAPI implements ILuaAPI
{
// attack
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Forward, side ) );
}
case 23:
{
// attackUp
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Up, side ) );
}
case 24:
{
// attackDown
Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Down, side ) );
}
case 25:
{
// dropUp
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Up, count ) );
}
case 26:
{
// dropDown
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Down, count ) );
}
case 27:
{
// suck
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Forward, count ) );
}
case 28:
{
// suckUp
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Up, count ) );
}
case 29:
{
// suckDown
int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Down, count ) );
}
case 30:
@@ -389,11 +411,13 @@ public class TurtleAPI implements ILuaAPI
case 36:
{
// equipLeft
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Left ) );
}
case 37:
{
// equipRight
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Right ) );
}
case 38:

View File

@@ -11,10 +11,16 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.entity.Entity;
import net.minecraft.entity.IMerchant;
import net.minecraft.entity.passive.AbstractHorse;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntitySign;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IInteractionObject;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.util.FakePlayer;
@@ -27,7 +33,7 @@ public class TurtlePlayer extends FakePlayer
{
public final static GameProfile DEFAULT_PROFILE = new GameProfile(
UUID.fromString( "0d0c4ca0-4ff1-11e4-916c-0800200c9a66" ),
"ComputerCraft"
"[ComputerCraft]"
);
@Deprecated
@@ -87,6 +93,12 @@ public class TurtlePlayer extends FakePlayer
return results;
}
@Override
public Vec3d getPositionVector()
{
return new Vec3d( posX, posY, posZ );
}
@Override
public float getEyeHeight()
{
@@ -99,6 +111,63 @@ public class TurtlePlayer extends FakePlayer
return 0.0f;
}
@Override
public void sendEnterCombat()
{
}
@Override
public void sendEndCombat()
{
}
@Nonnull
@Override
public SleepResult trySleep( @Nonnull BlockPos bedLocation )
{
return SleepResult.OTHER_PROBLEM;
}
@Override
public void openEditSign( TileEntitySign signTile )
{
}
@Override
public void displayGui( IInteractionObject guiOwner )
{
}
@Override
public void displayGUIChest( IInventory chestInventory )
{
}
@Override
public void displayVillagerTradeGui( IMerchant villager )
{
}
@Override
public void openGuiHorseInventory( AbstractHorse horse, IInventory inventoryIn )
{
}
@Override
public void openBook( ItemStack stack, @Nonnull EnumHand hand )
{
}
@Override
public void updateHeldItem()
{
}
@Override
protected void onItemUseFinish()
{
}
@Override
public void mountEntityAndWakeUp()
{
@@ -108,9 +177,4 @@ public class TurtlePlayer extends FakePlayer
public void dismountEntity( @Nonnull Entity entity )
{
}
@Override
public void openEditSign( TileEntitySign signTile )
{
}
}

View File

@@ -170,6 +170,17 @@ public abstract class ItemTurtleBase extends ItemComputerBase implements ITurtle
}
}
@Override
public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family )
{
return TurtleItemFactory.create(
getComputerID( stack ), getLabel( stack ),
getColour( stack ), family,
getUpgrade( stack, TurtleSide.Left ), getUpgrade( stack, TurtleSide.Right ),
getFuelLevel( stack ), getOverlay( stack )
);
}
@Override
public ItemStack setColour( ItemStack stack, int colour )
{

View File

@@ -7,82 +7,41 @@
package dan200.computercraft.shared.turtle.recipes;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.util.RecipeUtil;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapedRecipes;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.world.World;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.IRecipeFactory;
import net.minecraftforge.common.crafting.JsonContext;
import javax.annotation.Nonnull;
public class TurtleRecipe extends ShapedRecipes
public class TurtleRecipe extends ComputerConvertRecipe
{
private final NonNullList<Ingredient> m_recipe;
private final ComputerFamily m_family;
private final ComputerFamily family;
public TurtleRecipe( String group, int width, int height, NonNullList<Ingredient> recipe, ComputerFamily family )
public TurtleRecipe( String group, @Nonnull CraftingHelper.ShapedPrimer primer, ComputerFamily family )
{
super( group, width, height, recipe, TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null ) );
m_recipe = recipe;
m_family = family;
}
@Override
public boolean matches( @Nonnull InventoryCrafting _inventory, @Nonnull World world )
{
return !getCraftingResult( _inventory ).isEmpty();
super( group, primer, TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null ) );
this.family = family;
}
@Nonnull
@Override
public ItemStack getCraftingResult( @Nonnull InventoryCrafting inventory )
protected ItemStack convert( @Nonnull ItemStack stack )
{
// See if we match the recipe, and extract the input computercraft ID
int computerID = -1;
String label = null;
for( int y = 0; y < 3; ++y )
{
for( int x = 0; x < 3; ++x )
{
ItemStack item = inventory.getStackInRowAndColumn( x, y );
Ingredient target = m_recipe.get( x + y * 3 );
IComputerItem item = (IComputerItem) stack.getItem();
int computerID = item.getComputerID( stack );
String label = item.getLabel( stack );
if( item.getItem() instanceof IComputerItem )
{
IComputerItem itemComputer = (IComputerItem) item.getItem();
if( itemComputer.getFamily( item ) != m_family ) return ItemStack.EMPTY;
if( family == ComputerFamily.Beginners ) computerID = -1;
computerID = itemComputer.getComputerID( item );
label = itemComputer.getLabel( item );
}
else if( !target.apply( item ) )
{
return ItemStack.EMPTY;
}
}
}
// Build a turtle with the same ID the computer had
// Construct the new stack
if( m_family != ComputerFamily.Beginners )
{
return TurtleItemFactory.create( computerID, label, -1, m_family, null, null, 0, null );
}
else
{
return TurtleItemFactory.create( -1, label, -1, m_family, null, null, 0, null );
}
return TurtleItemFactory.create( computerID, label, -1, family, null, null, 0, null );
}
public static class Factory implements IRecipeFactory
@@ -91,20 +50,10 @@ public class TurtleRecipe extends ShapedRecipes
public IRecipe parse( JsonContext context, JsonObject json )
{
String group = JsonUtils.getString( json, "group", "" );
String familyName = JsonUtils.getString( json, "family" );
ComputerFamily family;
try
{
family = ComputerFamily.valueOf( familyName );
}
catch( IllegalArgumentException e )
{
throw new JsonSyntaxException( "Unknown computer family '" + familyName + "'" );
}
ComputerFamily family = RecipeUtil.getFamily( json, "family" );
CraftingHelper.ShapedPrimer primer = RecipeUtil.getPrimer( context, json );
return new TurtleRecipe( group, primer.width, primer.height, primer.input, family );
return new TurtleRecipe( group, primer, family );
}
}
}

View File

@@ -37,7 +37,7 @@ public class TurtleUpgradeRecipe extends IForgeRegistryEntry.Impl<IRecipe> imple
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

View File

@@ -9,6 +9,7 @@ package dan200.computercraft.shared.util;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.*;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
@@ -102,4 +103,17 @@ public class RecipeUtil
return ings;
}
public static ComputerFamily getFamily( JsonObject json, String name )
{
String familyName = JsonUtils.getString( json, name );
try
{
return ComputerFamily.valueOf( familyName );
}
catch( IllegalArgumentException e )
{
throw new JsonSyntaxException( "Unknown computer family '" + familyName + "' for field " + name );
}
}
}

View File

@@ -126,22 +126,8 @@ public class WorldUtil
}
public static Vec3d getRayEnd( EntityPlayer player) {
double reach = 4.5;
if( player instanceof EntityPlayerMP )
{
reach = ((EntityPlayerMP) player).interactionManager.getBlockReachDistance();
}
else if( player.getEntityWorld().isRemote )
{
reach = Minecraft.getMinecraft().playerController.getBlockReachDistance();
}
else if( player.capabilities.isCreativeMode )
{
reach = 5.0;
}
double reach = player.getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue();
Vec3d look = player.getLookVec();
return getRayStart( player ).addVector( look.x * reach, look.y * reach, look.z * reach );
}

View File

@@ -0,0 +1,69 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import net.minecraft.nbt.NBTBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import javax.annotation.Nonnull;
public class CapabilityWiredElement
{
@CapabilityInject( IWiredElement.class )
public static Capability<IWiredElement> CAPABILITY = null;
public static void register()
{
CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage(), NullElement::new );
}
private static class NullElement implements IWiredElement
{
@Nonnull
@Override
public IWiredNode getNode()
{
throw new IllegalStateException( "Should not use the default element implementation" );
}
@Nonnull
@Override
public World getWorld()
{
throw new IllegalStateException( "Should not use the default element implementation" );
}
@Nonnull
@Override
public Vec3d getPosition()
{
throw new IllegalStateException( "Should not use the default element implementation" );
}
@Nonnull
@Override
public String getSenderID()
{
throw new IllegalStateException( "Should not use the default element implementation" );
}
}
private static class NullStorage implements Capability.IStorage<IWiredElement>
{
@Override
public NBTBase writeNBT( Capability<IWiredElement> capability, IWiredElement instance, EnumFacing side )
{
return null;
}
@Override
public void readNBT( Capability<IWiredElement> capability, IWiredElement instance, EnumFacing side, NBTBase base )
{
}
}
}

View File

@@ -1,23 +0,0 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredElementTile;
import dan200.computercraft.api.network.wired.IWiredProvider;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class DefaultWiredProvider implements IWiredProvider
{
@Nullable
@Override
public IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
TileEntity te = world.getTileEntity( pos );
return te instanceof IWiredElementTile ? ((IWiredElementTile) te).getWiredElement( side ) : null;
}
}

View File

@@ -1,5 +1,7 @@
package dan200.computercraft.shared.wired;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.network.Packet;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNode;
@@ -283,9 +285,10 @@ public final class WiredNetwork implements IWiredNetwork
}
@Override
public void invalidate( @Nonnull IWiredNode node )
public void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> newPeripherals )
{
WiredNode wired = checkNode( node );
Preconditions.checkNotNull( peripherals, "peripherals cannot be null" );
lock.writeLock().lock();
try
@@ -293,11 +296,10 @@ public final class WiredNetwork implements IWiredNetwork
if( wired.network != this ) throw new IllegalStateException( "Node is not on this network" );
Map<String, IPeripheral> oldPeripherals = wired.peripherals;
Map<String, IPeripheral> newPeripherals = wired.element.getPeripherals();
WiredNetworkChange change = WiredNetworkChange.changeOf( oldPeripherals, newPeripherals );
if( change.isEmpty() ) return;
wired.peripherals = newPeripherals;
wired.peripherals = ImmutableMap.copyOf( newPeripherals );
// Detach the old peripherals then remove them.
peripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );

View File

@@ -10,6 +10,8 @@ import java.util.Map;
public class WiredNetworkChange implements IWiredNetworkChange
{
private static final WiredNetworkChange EMPTY = new WiredNetworkChange( Collections.emptyMap(), Collections.emptyMap() );
private final Map<String, IPeripheral> removed;
private final Map<String, IPeripheral> added;
@@ -26,16 +28,30 @@ public class WiredNetworkChange implements IWiredNetworkChange
public static WiredNetworkChange added( Map<String, IPeripheral> added )
{
return new WiredNetworkChange( Collections.emptyMap(), Collections.unmodifiableMap( added ) );
return added.isEmpty() ? EMPTY : new WiredNetworkChange( Collections.emptyMap(), Collections.unmodifiableMap( added ) );
}
public static WiredNetworkChange removed( Map<String, IPeripheral> removed )
{
return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.emptyMap() );
return removed.isEmpty() ? EMPTY : new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.emptyMap() );
}
public static WiredNetworkChange changeOf( Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals )
{
// Handle the trivial cases, where all peripherals have been added or removed.
if( oldPeripherals.isEmpty() && newPeripherals.isEmpty() )
{
return EMPTY;
}
else if( oldPeripherals.isEmpty() )
{
return new WiredNetworkChange( Collections.emptyMap(), newPeripherals );
}
else if( newPeripherals.isEmpty() )
{
return new WiredNetworkChange( oldPeripherals, Collections.emptyMap() );
}
Map<String, IPeripheral> added = new HashMap<>( newPeripherals );
Map<String, IPeripheral> removed = new HashMap<>();

View File

@@ -662,8 +662,36 @@ end
if http then
local nativeHTTPRequest = http.request
local function wrapRequest( _url, _post, _headers, _binary )
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary )
local methods = {
GET = true, POST = true, HEAD = true,
OPTIONS = true, PUT = true, DELETE = true
}
local function checkKey( options, key, ty, opt )
local value = options[key]
local valueTy = type(value)
if (value ~= nil or not opt) and valueTy ~= ty then
error(("bad field '%s' (expected %s, got %s"):format(key, ty, valueTy), 4)
end
end
local function checkOptions( options, body )
checkKey( options, "url", "string")
if body == false
then checkKey( options, "body", "nil" )
else checkKey( options, "body", "string", not body ) end
checkKey( options, "headers", "table", true )
checkKey( options, "method", "string", true )
checkKey( options, "redirect", "boolean", true )
if options.method and not methods[options.method] then
error( "Unsupported HTTP method", 3 )
end
end
local function wrapRequest( _url, ... )
local ok, err = nativeHTTPRequest( ... )
if ok then
while true do
local event, param1, param2, param3 = os.pullEvent()
@@ -678,6 +706,11 @@ if http then
end
http.get = function( _url, _headers, _binary)
if type( _url ) == "table" then
checkOptions( _url, false )
return wrapRequest( _url.url, _url )
end
if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end
@@ -687,10 +720,15 @@ if http then
if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #3 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end
return wrapRequest( _url, nil, _headers, _binary)
return wrapRequest( _url, _url, nil, _headers, _binary )
end
http.post = function( _url, _post, _headers, _binary)
if type( _url ) == "table" then
checkOptions( _url, true )
return wrapRequest( _url.url, _url )
end
if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end
@@ -703,25 +741,34 @@ if http then
if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end
return wrapRequest( _url, _post or "", _headers, _binary)
return wrapRequest( _url, _url, _post, _headers, _binary )
end
http.request = function( _url, _post, _headers, _binary )
if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end
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 )
end
if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
local url
if type( _url ) == "table" then
checkOptions( _url )
url = _url.url
else
if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end
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 )
end
if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end
url = _url.url
end
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary )
if not ok then
os.queueEvent( "http_failure", _url, err )
os.queueEvent( "http_failure", url, err )
end
return ok, err
end

View File

@@ -16,7 +16,7 @@ local tKeys = {
"comma", "period", "slash", "rightShift","multiply", -- 51
"leftAlt", "space", "capsLock", "f1", "f2", -- 56
"f3", "f4", "f5", "f6", "f7", -- 61
"f8", "f9", "f10", "numLock", "scollLock", -- 66
"f8", "f9", "f10", "numLock", "scrollLock", -- 66
"numPad7", "numPad8", "numPad9", "numPadSubtract","numPad4", -- 71
"numPad5", "numPad6", "numPadAdd","numPad1", "numPad2", -- 76
"numPad3", "numPad0", "numPadDecimal",nil, nil, -- 81
@@ -31,7 +31,7 @@ local tKeys = {
nil, nil, nil, nil, nil, -- 126
nil, nil, nil, nil, nil, -- 131
nil, nil, nil, nil, nil, -- 136
"numPadEquals",nil, nil, "cimcumflex","at", -- 141
"numPadEquals",nil, nil, "circumflex","at", -- 141
"colon", "underscore","kanji", "stop", "ax", -- 146
nil, nil, nil, nil, nil, -- 151
"numPadEnter","rightCtrl",nil, nil, nil, -- 156
@@ -53,6 +53,9 @@ for nKey, sKey in pairs( tKeys ) do
keys[sKey] = nKey
end
keys["return"] = keys.enter
--backwards compatibility to earlier, typo prone, versions
keys.scollLock = keys.scrollLock
keys.cimcumflex = keys.circumflex
function getName( _nKey )
if type( _nKey ) ~= "number" then

View File

@@ -1,5 +1,7 @@
wget is a program for downloading files from the internet. This is useful for downloading programs created by other players.
If no filename is specified wget will try to determine the filename from the URL by stripping any anchors, parameters and trailing slashes and then taking everything remaining after the last slash.
The HTTP API must be enabled in ComputerCraft.cfg to use this program.
ex:
"wget http://pastebin.com/raw/CxaWmPrX test" will download the file from the URL http://pastebin.com/raw/CxaWmPrX, and save it as "test".
"wget http://example.org/test.lua/?foo=bar#qzu" will download the file from the URL http://example.org/test.lua/?foo=bar#qzu and save it as "test.lua"
"wget http://example.org/" will download the file from the URL http://example.org and save it as "example.org"

View File

@@ -1,11 +1,11 @@
local function printUsage()
print( "Usage:" )
print( "wget <url> <filename>" )
print( "wget <url> [filename]" )
end
local tArgs = { ... }
if #tArgs < 2 then
if #tArgs < 1 then
printUsage()
return
end
@@ -15,19 +15,15 @@ if not http then
printError( "Set http_enable to true in ComputerCraft.cfg" )
return
end
local function getFilename( sUrl )
sUrl = sUrl:gsub( "[#?].*" , "" ):gsub( "/+$" , "" )
return sUrl:match( "/([^/]+)$" )
end
local function get( sUrl )
write( "Connecting to " .. sUrl .. "... " )
local ok, err = http.checkURL( sUrl )
if not ok then
print( "Failed." )
if err then
printError( err )
end
return nil
end
local response = http.get( sUrl , nil , true )
if not response then
print( "Failed." )
@@ -40,10 +36,18 @@ local function get( sUrl )
response.close()
return sResponse
end
-- Determine file to download
local sUrl = tArgs[1]
local sFile = tArgs[2]
--Check if the URL is valid
local ok, err = http.checkURL( sUrl )
if not ok then
printError( err or "Invalid URL." )
return
end
local sFile = tArgs[2] or getFilename( sUrl )
local sPath = shell.resolve( sFile )
if fs.exists( sPath ) then
print( "File already exists" )

View File

@@ -2,6 +2,10 @@
"recipes" : {
"impostor_shaped" : "dan200.computercraft.shared.util.ImpostorRecipe$Factory",
"impostor_shapeless" : "dan200.computercraft.shared.util.ImpostorShapelessRecipe$Factory",
"turtle" : "dan200.computercraft.shared.turtle.recipes.TurtleRecipe$Factory"
"turtle" : "dan200.computercraft.shared.turtle.recipes.TurtleRecipe$Factory",
"computer_upgrade" : "dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe$Factory"
},
"ingredients": {
"computer": "dan200.computercraft.shared.computer.recipe.ComputerIngredient$Factory"
}
}

View File

@@ -0,0 +1,14 @@
{
"type": "computercraft:computer_upgrade",
"pattern": [
"###",
"#C#",
"# #"
],
"key": {
"#": { "type": "forge:ore_dict", "ore": "ingotGold" },
"C": { "type": "computercraft:computer", "item": "computercraft:computer", "family": "Normal" }
},
"family": "Advanced",
"result": { "item": "computercraft:computer", "data": 16384 }
}

View File

@@ -0,0 +1,14 @@
{
"type": "computercraft:computer_upgrade",
"pattern": [
"###",
"#C#",
"# #"
],
"key": {
"#": { "type": "forge:ore_dict", "ore": "ingotGold" },
"C": { "item": "computercraft:pocket_computer", "data": 0 }
},
"family": "Advanced",
"result": { "item": "computercraft:pocket_computer", "data": 1 }
}

View File

@@ -7,7 +7,7 @@
],
"key": {
"#": { "type": "forge:ore_dict", "ore": "ingotGold" },
"C": { "item": "computercraft:computer", "data": 16384 },
"C": { "type": "computercraft:computer", "item": "computercraft:computer", "data": 16384, "family": "Advanced" },
"I": { "type": "forge:ore_dict", "ore": "chestWood" }
},
"family": "Advanced"

View File

@@ -0,0 +1,18 @@
{
"type": "computercraft:computer_upgrade",
"pattern": [
"###",
"#C#",
" B "
],
"key": {
"#": { "type": "forge:ore_dict", "ore": "ingotGold" },
"B": { "type": "forge:ore_dict", "ore": "blockGold" },
"C": [
{ "type": "computercraft:computer", "item": "computercraft:turtle", "family": "Normal" },
{ "item": "computercraft:turtle_expanded", "data": 0 }
]
},
"family": "Advanced",
"result": { "item": "computercraft:turtle_advanced", "data": 0 }
}

View File

@@ -7,7 +7,7 @@
],
"key": {
"#": { "type": "forge:ore_dict", "ore": "ingotIron" },
"C": { "item": "computercraft:computer", "data": 0 },
"C": { "type": "computercraft:computer", "item": "computercraft:computer", "family": "Normal" },
"I": { "type": "forge:ore_dict", "ore": "chestWood" }
},
"family": "Normal"

View File

@@ -23,7 +23,6 @@ import org.junit.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -379,17 +378,10 @@ public class NetworkTest
remotePeripherals.putAll( change.peripheralsAdded() );
}
@Nonnull
@Override
public Map<String, IPeripheral> getPeripherals()
{
return Collections.unmodifiableMap( localPeripherals );
}
public NetworkElement addPeripheral( String name )
{
localPeripherals.put( name, new NetworkPeripheral() );
getNode().invalidate();
getNode().updatePeripherals( localPeripherals );
return this;
}