mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Merge branch 'mc-1.16.x' into mc-1.17.x
This commit is contained in:
		| @@ -174,7 +174,7 @@ dependencies { | ||||
|     testModImplementation sourceSets.main.output | ||||
|     testModExtra 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21' | ||||
| 
 | ||||
|     cctJavadoc 'cc.tweaked:cct-javadoc:1.4.4' | ||||
|     cctJavadoc 'cc.tweaked:cct-javadoc:1.4.5' | ||||
| } | ||||
| 
 | ||||
| // Compile tasks | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| module: [kind=event] modem_message | ||||
| --- | ||||
| 
 | ||||
| The @{modem_message} event is fired when a message is received on an open channel on any modem. | ||||
| The @{modem_message} event is fired when a message is received on an open channel on any @{modem}. | ||||
| 
 | ||||
| ## Return Values | ||||
| 1. @{string}: The event name. | ||||
| @@ -10,11 +10,15 @@ The @{modem_message} event is fired when a message is received on an open channe | ||||
| 3. @{number}: The channel that the message was sent on. | ||||
| 4. @{number}: The reply channel set by the sender. | ||||
| 5. @{any}: The message as sent by the sender. | ||||
| 6. @{number}: The distance between the sender and the receiver, in blocks (decimal). | ||||
| 6. @{number}: The distance between the sender and the receiver, in blocks. | ||||
| 
 | ||||
| ## Example | ||||
| Prints a message when one is sent: | ||||
| Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages. | ||||
| 
 | ||||
| ```lua | ||||
| local modem = peripheral.find("modem") or error("No modem attached", 0) | ||||
| modem.open(0) | ||||
| 
 | ||||
| while true do | ||||
|   local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") | ||||
|   print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message))) | ||||
|   | ||||
							
								
								
									
										83
									
								
								doc/guides/using_require.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								doc/guides/using_require.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| --- | ||||
| module: [kind=guide] using_require | ||||
| --- | ||||
| 
 | ||||
| # Reusing code with require | ||||
| A library is a collection of useful functions and other definitions which is stored separately to your main program. You | ||||
| might want to create a library because you have some functions which are used in multiple programs, or just to split | ||||
| your program into multiple more modular files. | ||||
| 
 | ||||
| Let's say we want to create a small library to make working with the @{term|terminal} a little easier. We'll provide two | ||||
| functions: `reset`, which clears the terminal and sets the cursor to (1, 1), and `write_center`, which prints some text | ||||
| in the middle of the screen. | ||||
| 
 | ||||
| Start off by creating a file called `more_term.lua`: | ||||
| 
 | ||||
| ```lua {data-snippet=more_term} | ||||
| local function reset() | ||||
|   term.clear() | ||||
|   term.setCursorPos(1, 1) | ||||
| end | ||||
| 
 | ||||
| local function write_center(text) | ||||
|   local x, y = term.getCursorPos() | ||||
|   local width, height = term.getSize() | ||||
|   term.setCursorPos(math.floor((width - #text) / 2) + 1, y) | ||||
|   term.write(text) | ||||
| end | ||||
| 
 | ||||
| return { reset = reset, write_center = write_center } | ||||
| ``` | ||||
| 
 | ||||
| Now, what's going on here? We define our two functions as one might expect, and then at the bottom return a table with | ||||
| the two functions. When we require this library, this table is what is returned. With that, we can then call the | ||||
| original functions. Now create a new file, with the following: | ||||
| 
 | ||||
| ```lua {data-mount=more_term:more_term.lua} | ||||
| local more_term = require("more_term") | ||||
| more_term.reset() | ||||
| more_term.write_center("Hello, world!") | ||||
| ``` | ||||
| 
 | ||||
| When run, this'll clear the screen and print some text in the middle of the first line. | ||||
| 
 | ||||
| ## require in depth | ||||
| While the previous section is a good introduction to how @{require} operates, there are a couple of remaining points | ||||
| which are worth mentioning for more advanced usage. | ||||
| 
 | ||||
| ### Libraries can return anything | ||||
| In our above example, we return a table containing the functions we want to expose. However, it's worth pointing out | ||||
| that you can return ''anything'' from your library - a table, a function or even just a string! @{require} treats them | ||||
| all the same, and just returns whatever your library provides. | ||||
| 
 | ||||
| ### Module resolution and the package path | ||||
| In the above examples, we defined our library in a file, and @{require} read from it. While this is what you'll do most | ||||
| of the time, it is possible to make @{require} look elsewhere for your library, such as downloading from a website or | ||||
| loading from an in-memory library store. | ||||
| 
 | ||||
| As a result, the *module name* you pass to @{require} doesn't correspond to a file path. One common mistake is to load | ||||
| code from a sub-directory using `require("folder/library")` or even `require("folder/library.lua")`, neither of which | ||||
| will do quite what you expect. | ||||
| 
 | ||||
| When loading libraries (also referred to as *modules*) from files, @{require} searches along the *@{package.path|module | ||||
| path}*. By default, this looks something like: | ||||
| 
 | ||||
| * `?.lua` | ||||
| * `?/init.lua` | ||||
| * `/rom/modules/main/?.lua` | ||||
| * etc... | ||||
| 
 | ||||
| When you call `require("my_library")`, @{require} replaces the `?` in each element of the path with your module name, and | ||||
| checks if the file exists. In this case, we'd look for `my_library.lua`, `my_library/init.lua`, | ||||
| `/rom/modules/main/my_library.lua` and so on. Note that this works *relative to the current program*, so if your | ||||
| program is actually called `folder/program`, then we'll look for `folder/my_library.lua`, etc... | ||||
| 
 | ||||
| One other caveat is loading libraries from sub-directories. For instance, say we have a file | ||||
| `my/fancy/library.lua`. This can be loaded by using `require("my.fancy.library")` - the '.'s are replaced with '/' | ||||
| before we start looking for the library. | ||||
| 
 | ||||
| ## External links | ||||
| There are several external resources which go into require in a little more detail: | ||||
| 
 | ||||
|  - The [Lua Module tutorial](http://lua-users.org/wiki/ModulesTutorial) on the Lua wiki. | ||||
|  - [Lua's manual section on @{require}](https://www.lua.org/manual/5.1/manual.html#pdf-require). | ||||
| @@ -12,16 +12,19 @@ rounded up to the nearest multiple of 0.05 seconds. If you are using coroutines | ||||
| or the @{parallel|parallel API}, it will only pause execution of the current | ||||
| thread, not the whole program. | ||||
|  | ||||
| **Note** Because sleep internally uses timers, it is a function that yields. | ||||
| This means that you can use it to prevent "Too long without yielding" errors, | ||||
| however, as the minimum sleep time is 0.05 seconds, it will slow your program | ||||
| down. | ||||
| :::tip | ||||
| Because sleep internally uses timers, it is a function that yields. This means | ||||
| that you can use it to prevent "Too long without yielding" errors, however, as | ||||
| the minimum sleep time is 0.05 seconds, it will slow your program down. | ||||
| ::: | ||||
|  | ||||
| **Warning** Internally, this function queues and waits for a timer event (using | ||||
| :::caution | ||||
| Internally, this function queues and waits for a timer event (using | ||||
| @{os.startTimer}), however it does not listen for any other events. This means | ||||
| that any event that occurs while sleeping will be entirely discarded. If you | ||||
| need to receive events while sleeping, consider using @{os.startTimer|timers}, | ||||
| or the @{parallel|parallel API}. | ||||
| ::: | ||||
|  | ||||
| @tparam number time The number of seconds to sleep for, rounded up to the | ||||
| nearest multiple of 0.05. | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| org.gradle.jvmargs=-Xmx3G | ||||
|  | ||||
| # Mod properties | ||||
| mod_version=1.99.1 | ||||
| mod_version=1.100.0 | ||||
|  | ||||
| # Minecraft properties (update mods.toml when changing) | ||||
| mc_version=1.17.1 | ||||
|   | ||||
| @@ -5,9 +5,8 @@ | ||||
|  */ | ||||
| package dan200.computercraft.api.lua; | ||||
| 
 | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import static dan200.computercraft.api.lua.LuaValues.*; | ||||
|   | ||||
| @@ -115,7 +115,9 @@ class DfpwmStream implements AudioStream | ||||
|         } | ||||
| 
 | ||||
|         result.flip(); | ||||
|         return result; | ||||
| 
 | ||||
|         // This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted. | ||||
|         return result.remaining() == 0 ? null : result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -123,4 +125,9 @@ class DfpwmStream implements AudioStream | ||||
|     { | ||||
|         buffers.clear(); | ||||
|     } | ||||
| 
 | ||||
|     public boolean isEmpty() | ||||
|     { | ||||
|         return buffers.isEmpty(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,8 +27,20 @@ public class SpeakerInstance | ||||
| 
 | ||||
|     public synchronized void pushAudio( ByteBuf buffer ) | ||||
|     { | ||||
|         if( currentStream == null ) currentStream = new DfpwmStream(); | ||||
|         SpeakerSound sound = this.sound; | ||||
| 
 | ||||
|         DfpwmStream stream = currentStream; | ||||
|         if( stream == null ) stream = currentStream = new DfpwmStream(); | ||||
|         boolean exhausted = stream.isEmpty(); | ||||
|         currentStream.push( buffer ); | ||||
| 
 | ||||
|         // If we've got nothing left in the buffer, enqueue an additional one just in case. | ||||
|         if( exhausted && sound != null && sound.stream == stream && sound.channel != null ) | ||||
|         { | ||||
|             sound.executor.execute( () -> { | ||||
|                 if( !sound.channel.stopped() ) sound.channel.pumpBuffers( 1 ); | ||||
|             } ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void playAudio( Vec3 position, float volume ) | ||||
|   | ||||
| @@ -26,12 +26,14 @@ public class SpeakerManager | ||||
|     @SubscribeEvent | ||||
|     public static void playStreaming( PlayStreamingSourceEvent event ) | ||||
|     { | ||||
|         if( !(event.getSound() instanceof SpeakerSound) ) return; | ||||
|         SpeakerSound sound = (SpeakerSound) event.getSound(); | ||||
|         if( !(event.getSound() instanceof SpeakerSound sound) ) return; | ||||
|         if( sound.stream == null ) return; | ||||
| 
 | ||||
|         event.getSource().attachBufferStream( sound.stream ); | ||||
|         event.getSource().play(); | ||||
| 
 | ||||
|         sound.channel = event.getSource(); | ||||
|         sound.executor = event.getManager().executor; | ||||
|     } | ||||
| 
 | ||||
|     public static SpeakerInstance getSound( UUID source ) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  */ | ||||
| package dan200.computercraft.client.sound; | ||||
| 
 | ||||
| import com.mojang.blaze3d.audio.Channel; | ||||
| import net.minecraft.client.resources.sounds.AbstractSoundInstance; | ||||
| import net.minecraft.client.resources.sounds.TickableSoundInstance; | ||||
| import net.minecraft.client.sounds.AudioStream; | ||||
| @@ -13,9 +14,12 @@ import net.minecraft.sounds.SoundSource; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance | ||||
| { | ||||
|     Channel channel; | ||||
|     Executor executor; | ||||
|     DfpwmStream stream; | ||||
| 
 | ||||
|     SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch ) | ||||
|   | ||||
| @@ -30,7 +30,36 @@ import java.util.OptionalLong; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| /** | ||||
|  * The FS API allows you to manipulate files and the filesystem. | ||||
|  * The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and | ||||
|  * paths. This includes: | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li>**Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or | ||||
|  * write to a file.</li> | ||||
|  * <li>**Path manipulation:** {@link #combine}, {@link #getName} and {@link #getDir} allow you to manipulate file | ||||
|  * paths, joining them together or extracting components.</li> | ||||
|  * <li>**Querying paths:** For instance, checking if a file exists, or whether it's a directory. See {@link #getSize}, | ||||
|  * {@link #exists}, {@link #isDir}, {@link #isReadOnly} and {@link #attributes}.</li> | ||||
|  * <li>**File and directory manipulation:** For instance, moving or copying files. See {@link #makeDir}, {@link #move}, | ||||
|  * {@link #copy} and {@link #delete}.</li> | ||||
|  * </ul> | ||||
|  * | ||||
|  * :::note | ||||
|  * All functions in the API work on absolute paths, and do not take the @{shell.dir|current directory} into account. | ||||
|  * You can use @{shell.resolve} to convert a relative path into an absolute one. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Mounts | ||||
|  * While a computer can only have one hard drive and filesystem, other filesystems may be "mounted" inside it. For | ||||
|  * instance, the {@link dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral drive peripheral} mounts | ||||
|  * its disk's contents at {@code "disk/"}, {@code "disk1/"}, etc... | ||||
|  * | ||||
|  * You can see which mount a path belongs to with the {@link #getDrive} function. This returns {@code "hdd"} for the | ||||
|  * computer's main filesystem ({@code "/"}), {@code "rom"} for the rom ({@code "rom/"}). | ||||
|  * | ||||
|  * Most filesystems have a limited capacity, operations which would cause that capacity to be reached (such as writing | ||||
|  * an incredibly large file) will fail. You can see a mount's capacity with {@link #getCapacity} and the remaining | ||||
|  * space with {@link #getFreeSpace}. | ||||
|  * | ||||
|  * @cc.module fs | ||||
|  */ | ||||
| @@ -440,6 +469,12 @@ public class FSAPI implements ILuaAPI | ||||
|      * @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. | ||||
|      * @throws LuaException If the path doesn't exist. | ||||
|      * @cc.treturn string The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. | ||||
|      * @cc.usage Print the drives of a couple of mounts: | ||||
|      * | ||||
|      * <pre>{@code | ||||
|      * print("/: " .. fs.getDrive("/")) | ||||
|      * print("/rom/: " .. fs.getDrive("rom")) | ||||
|      * }</pre> | ||||
|      */ | ||||
|     @LuaFunction | ||||
|     public final Object[] getDrive( String path ) throws LuaException | ||||
|   | ||||
| @@ -22,7 +22,7 @@ import dan200.computercraft.core.computer.ComputerSide; | ||||
|  * as those from Project:Red. These allow you to send 16 separate on/off signals. Each channel corresponds to a | ||||
|  * colour, with the first being @{colors.white} and the last @{colors.black}. | ||||
|  * | ||||
|  * Whenever a redstone input changes, a {@code redstone} event will be fired. This may be used instead of repeativly | ||||
|  * Whenever a redstone input changes, a @{event!redstone} event will be fired. This may be used instead of repeativly | ||||
|  * polling. | ||||
|  * | ||||
|  * This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of | ||||
|   | ||||
| @@ -19,10 +19,10 @@ import javax.annotation.Nonnull; | ||||
|  * | ||||
|  * This works with energy storage blocks, as well as generators and machines which consume energy. | ||||
|  * | ||||
|  * <blockquote> | ||||
|  * <strong>Note:</strong> Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF | ||||
|  * :::note | ||||
|  * Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF | ||||
|  * used/generated per tick). | ||||
|  * </blockquote> | ||||
|  * ::: | ||||
|  * | ||||
|  * @cc.module energy_storage | ||||
|  */ | ||||
|   | ||||
| @@ -21,9 +21,60 @@ import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * The modem peripheral allows you to send messages between computers. | ||||
|  * Modems allow you to send messages between computers over long distances. | ||||
|  * | ||||
|  * :::tip | ||||
|  * Modems provide a fairly basic set of methods, which makes them very flexible but often hard to work with. The | ||||
|  * {@literal @}{rednet} API is built on top of modems, and provides a more user-friendly interface. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Sending and receiving messages | ||||
|  * Modems operate on a series of channels, a bit like frequencies on a radio. Any modem can send a message on a | ||||
|  * particular channel, but only those which have {@link #open opened} the channel and are "listening in" can receive | ||||
|  * messages. | ||||
|  * | ||||
|  * Channels are represented as an integer between 0 and 65535 inclusive. These channels don't have any defined meaning, | ||||
|  * though some APIs or programs will assign a meaning to them. For instance, the @{gps} module sends all its messages on | ||||
|  * channel 65534 (@{gps.CHANNEL_GPS}), while @{rednet} uses channels equal to the computer's ID. | ||||
|  * | ||||
|  * - Sending messages is done with the {@link #transmit(int, int, Object)} message. | ||||
|  * - Receiving messages is done by listening to the @{modem_message} event. | ||||
|  * | ||||
|  * ## Types of modem | ||||
|  * CC: Tweaked comes with three kinds of modem, with different capabilities. | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li><strong>Wireless modems:</strong> Wireless modems can send messages to any other wireless modem. They can be placed next to a | ||||
|  * computer, or equipped as a pocket computer or turtle upgrade. | ||||
|  * | ||||
|  * Wireless modems have a limited range, only sending messages to modems within 64 blocks. This range increases | ||||
|  * linearly once the modem is above y=96, to a maximum of 384 at world height.</li> | ||||
|  * <li><strong>Ender modems:</strong> These are upgraded versions of normal wireless modems. They do not have a distance | ||||
|  * limit, and can send messages between dimensions.</li> | ||||
|  * <li><strong>Wired modems:</strong> These send messages to other any other wired modems connected to the same network | ||||
|  * (using <em>Networking Cable</em>). They also can be used to attach additional peripherals to a computer.</li></ul> | ||||
|  * | ||||
|  * @cc.module modem | ||||
|  * @cc.see modem_message Queued when a modem receives a message on an {@link #open(int) open channel}. | ||||
|  * @cc.see rednet A networking API built on top of the modem peripheral. | ||||
|  * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. Then wait for a message to | ||||
|  * arrive on channel 43 and print it. | ||||
|  * | ||||
|  * <pre>{@code | ||||
|  * local modem = peripheral.find("modem") or error("No modem attached", 0) | ||||
|  * modem.open(43) -- Open 43 so we can receive replies | ||||
|  * | ||||
|  * -- Send our message | ||||
|  * modem.transmit(15, 43, "Hello, world!") | ||||
|  * | ||||
|  * -- And wait for a reply | ||||
|  * local event, side, channel, replyChannel, message, distance | ||||
|  * repeat | ||||
|  *   event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") | ||||
|  * until channel == 43 | ||||
|  * | ||||
|  * print("Received a reply: " .. tostring(message)) | ||||
|  * }</pre> | ||||
|  */ | ||||
| public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver | ||||
| { | ||||
| @@ -157,12 +208,23 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa | ||||
|      * Sends a modem message on a certain channel. Modems listening on the channel will queue a {@code modem_message} | ||||
|      * event on adjacent computers. | ||||
|      * | ||||
|      * <blockquote><strong>Note:</strong> The channel does not need be open to send a message.</blockquote> | ||||
|      * :::note | ||||
|      * The channel does not need be open to send a message. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param channel      The channel to send messages on. | ||||
|      * @param replyChannel The channel that responses to this message should be sent on. | ||||
|      * @param payload      The object to send. This can be a boolean, string, number, or table. | ||||
|      * @param replyChannel The channel that responses to this message should be sent on. This can be the same as | ||||
|      *                     {@code channel} or entirely different. The channel must have been {@link #open opened} on | ||||
|      *                     the sending computer in order to receive the replies. | ||||
|      * @param payload      The object to send. This can be any primitive type (boolean, number, string) as well as | ||||
|      *                     tables. Other types (like functions), as well as metatables, will not be transmitted. | ||||
|      * @throws LuaException If the channel is out of range. | ||||
|      * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. | ||||
|      * | ||||
|      * <pre>{@code | ||||
|      * local modem = peripheral.find("modem") or error("No modem attached", 0) | ||||
|      * modem.transmit(15, 43, "Hello, world!") | ||||
|      * }</pre> | ||||
|      */ | ||||
|     @LuaFunction | ||||
|     public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException | ||||
|   | ||||
| @@ -80,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|      * If this computer is attached to the network, it _will not_ be included in | ||||
|      * this list. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @return Remote peripheral names on the network. | ||||
| @@ -95,8 +96,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Determine if a peripheral is available on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -112,8 +114,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Get the type of a peripheral is available on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -132,8 +135,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Check a peripheral is of a particular type. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -153,8 +157,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Get all available methods for the remote peripheral with the given name. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -174,8 +179,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Call a method on a peripheral on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer  The calling computer. | ||||
|      * @param context   The Lua context we're executing in. | ||||
| @@ -204,8 +210,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|      * may be used by other computers on the network to wrap this computer as a | ||||
|      * peripheral. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @return The current computer's name. | ||||
|      * @cc.treturn string|nil The current computer's name on the wired network. | ||||
|   | ||||
| @@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.speaker; | ||||
| 
 | ||||
| import dan200.computercraft.api.lua.LuaException; | ||||
| import dan200.computercraft.api.lua.LuaTable; | ||||
| import dan200.computercraft.shared.util.PauseAwareTimer; | ||||
| import net.minecraft.util.Mth; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| @@ -27,7 +28,7 @@ class DfpwmState | ||||
|      * The minimum size of the client's audio buffer. Once we have less than this on the client, we should send another | ||||
|      * batch of audio. | ||||
|      */ | ||||
|     private static final long CLIENT_BUFFER = (long) (SECOND * 1.5); | ||||
|     private static final long CLIENT_BUFFER = (long) (SECOND * 0.5); | ||||
| 
 | ||||
|     private static final int PREC = 10; | ||||
| 
 | ||||
| @@ -36,7 +37,7 @@ class DfpwmState | ||||
|     private boolean previousBit = false; | ||||
| 
 | ||||
|     private boolean unplayed = true; | ||||
|     private long clientEndTime = System.nanoTime(); | ||||
|     private long clientEndTime = PauseAwareTimer.getTime(); | ||||
|     private float pendingVolume = 1.0f; | ||||
|     private ByteBuffer pendingAudio; | ||||
| 
 | ||||
| @@ -107,7 +108,7 @@ class DfpwmState | ||||
| 
 | ||||
|     boolean isPlaying() | ||||
|     { | ||||
|         return unplayed || clientEndTime >= System.nanoTime(); | ||||
|         return unplayed || clientEndTime >= PauseAwareTimer.getTime(); | ||||
|     } | ||||
| 
 | ||||
|     float getVolume() | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; | ||||
| import dan200.computercraft.shared.util.PauseAwareTimer; | ||||
| import net.minecraft.ResourceLocationException; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; | ||||
| @@ -119,7 +120,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         long now = System.nanoTime(); | ||||
|         long now = PauseAwareTimer.getTime(); | ||||
|         if( sound != null ) | ||||
|         { | ||||
|             lastPlayTime = clock; | ||||
| @@ -342,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|         // TODO: Use ArgumentHelpers instead? | ||||
|         int length = audio.length(); | ||||
|         if( length <= 0 ) throw new LuaException( "Cannot play empty audio" ); | ||||
|         if( length > 1024 * 16 * 8 ) throw new LuaException( "Audio data is too large" ); | ||||
|         if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" ); | ||||
| 
 | ||||
|         DfpwmState state; | ||||
|         synchronized( lock ) | ||||
| @@ -387,17 +388,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|         computers.remove( computer ); | ||||
|     } | ||||
| 
 | ||||
|     private static final class PendingSound | ||||
|     private record PendingSound(ResourceLocation location, float volume, float pitch) | ||||
|     { | ||||
|         final ResourceLocation location; | ||||
|         final float volume; | ||||
|         final float pitch; | ||||
| 
 | ||||
|         private PendingSound( ResourceLocation location, float volume, float pitch ) | ||||
|         { | ||||
|             this.location = location; | ||||
|             this.volume = volume; | ||||
|             this.pitch = pitch; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,6 @@ import dan200.computercraft.shared.common.IColouredItem; | ||||
| import dan200.computercraft.shared.computer.core.ClientComputer; | ||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | ||||
| import dan200.computercraft.shared.pocket.apis.PocketAPI; | ||||
| @@ -34,6 +33,7 @@ import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.InteractionResultHolder; | ||||
| import net.minecraft.world.entity.Entity; | ||||
| import net.minecraft.world.entity.item.ItemEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.CreativeModeTab; | ||||
| import net.minecraft.world.item.Item; | ||||
| @@ -80,53 +80,65 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         PocketUpgrades.getVanillaUpgrades().map( x -> create( -1, null, -1, x ) ).forEach( stacks::add ); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected ) | ||||
|     { | ||||
|         if( !world.isClientSide ) | ||||
|         { | ||||
|             // Server side | ||||
|             Container inventory = entity instanceof Player ? ((Player) entity).getInventory() : null; | ||||
|             PocketServerComputer computer = createServerComputer( world, inventory, entity, stack ); | ||||
|             if( computer != null ) | ||||
|     private boolean tick( @Nonnull ItemStack stack, @Nonnull Level world, @Nonnull Entity entity, @Nonnull PocketServerComputer computer ) | ||||
|     { | ||||
|         IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
| 
 | ||||
|                 // Ping computer | ||||
|                 computer.keepAlive(); | ||||
|         computer.setLevel( world ); | ||||
|         computer.updateValues( entity, stack, upgrade ); | ||||
| 
 | ||||
|         boolean changed = false; | ||||
| 
 | ||||
|         // Sync ID | ||||
|         int id = computer.getID(); | ||||
|         if( id != getComputerID( stack ) ) | ||||
|         { | ||||
|             changed = true; | ||||
|             setComputerID( stack, id ); | ||||
|                     if( inventory != null ) inventory.setChanged(); | ||||
|         } | ||||
| 
 | ||||
|         // Sync label | ||||
|         String label = computer.getLabel(); | ||||
|         if( !Objects.equal( label, getLabel( stack ) ) ) | ||||
|         { | ||||
|             changed = true; | ||||
|             setLabel( stack, label ); | ||||
|                     if( inventory != null ) inventory.setChanged(); | ||||
|         } | ||||
| 
 | ||||
|         // Update pocket upgrade | ||||
|                 if( upgrade != null ) | ||||
|         if( upgrade != null ) upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
| 
 | ||||
|         return changed; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected ) | ||||
|     { | ||||
|                     upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
|                 } | ||||
|             } | ||||
|         if( !world.isClientSide ) | ||||
|         { | ||||
|             Container inventory = entity instanceof Player player ? player.getInventory() : null; | ||||
|             PocketServerComputer computer = createServerComputer( world, entity, inventory, stack ); | ||||
|             computer.keepAlive(); | ||||
| 
 | ||||
|             boolean changed = tick( stack, world, entity, computer ); | ||||
|             if( changed && inventory != null ) inventory.setChanged(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Client side | ||||
|             createClientComputer( stack ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onEntityItemUpdate( ItemStack stack, ItemEntity entity ) | ||||
|     { | ||||
|         if( entity.level.isClientSide ) return false; | ||||
| 
 | ||||
|         PocketServerComputer computer = getServerComputer( stack ); | ||||
|         if( computer != null && tick( stack, entity.level, entity, computer ) ) entity.setItem( stack.copy() ); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Nonnull | ||||
|     @Override | ||||
|     public InteractionResultHolder<ItemStack> use( Level world, Player player, @Nonnull InteractionHand hand ) | ||||
| @@ -134,22 +146,18 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         ItemStack stack = player.getItemInHand( hand ); | ||||
|         if( !world.isClientSide ) | ||||
|         { | ||||
|             PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, stack ); | ||||
| 
 | ||||
|             boolean stop = false; | ||||
|             if( computer != null ) | ||||
|             { | ||||
|             PocketServerComputer computer = createServerComputer( world, player, player.getInventory(), stack ); | ||||
|             computer.turnOn(); | ||||
| 
 | ||||
|             boolean stop = false; | ||||
|             IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
|             if( upgrade != null ) | ||||
|             { | ||||
|                 computer.updateValues( player, stack, upgrade ); | ||||
|                 stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
|             } | ||||
|             } | ||||
| 
 | ||||
|             if( !stop && computer != null ) | ||||
|             if( !stop ) | ||||
|             { | ||||
|                 boolean isTypingOnly = hand == InteractionHand.OFF_HAND; | ||||
|                 new ComputerContainerData( computer ).open( player, new PocketComputerMenuProvider( computer, stack, this, hand, isTypingOnly ) ); | ||||
| @@ -207,17 +215,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return super.getCreatorModId( stack ); | ||||
|     } | ||||
| 
 | ||||
|     public PocketServerComputer createServerComputer( final Level world, Container inventory, Entity entity, @Nonnull ItemStack stack ) | ||||
|     @Nonnull | ||||
|     public PocketServerComputer createServerComputer( Level world, Entity entity, @Nullable Container inventory, @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         if( world.isClientSide ) return null; | ||||
|         if( world.isClientSide ) throw new IllegalStateException( "Cannot call createServerComputer on the client" ); | ||||
| 
 | ||||
|         PocketServerComputer computer; | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|         int sessionID = getSessionID( stack ); | ||||
|         int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID(); | ||||
| 
 | ||||
|         if( instanceID >= 0 && sessionID == correctSessionID && | ||||
|             ComputerCraft.serverComputerRegistry.contains( instanceID ) ) | ||||
|         if( instanceID >= 0 && sessionID == correctSessionID && ComputerCraft.serverComputerRegistry.contains( instanceID ) ) | ||||
|         { | ||||
|             computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ); | ||||
|         } | ||||
| @@ -235,13 +243,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|                 computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" ); | ||||
|                 setComputerID( stack, computerID ); | ||||
|             } | ||||
|             computer = new PocketServerComputer( | ||||
|                 world, | ||||
|                 computerID, | ||||
|                 getLabel( stack ), | ||||
|                 instanceID, | ||||
|                 getFamily() | ||||
|             ); | ||||
|             computer = new PocketServerComputer( world, computerID, getLabel( stack ), instanceID, getFamily() ); | ||||
|             computer.updateValues( entity, stack, getUpgrade( stack ) ); | ||||
|             computer.addAPI( new PocketAPI( computer ) ); | ||||
|             ComputerCraft.serverComputerRegistry.add( instanceID, computer ); | ||||
| @@ -251,15 +253,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return computer; | ||||
|     } | ||||
| 
 | ||||
|     public static ServerComputer getServerComputer( @Nonnull ItemStack stack ) | ||||
|     @Nullable | ||||
|     public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int session = getSessionID( stack ); | ||||
|         if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null; | ||||
| 
 | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|         return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get( instanceID ) : null; | ||||
|         return instanceID >= 0 ? (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ) : null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public static ClientComputer createClientComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int instanceID = getInstanceID( stack ); | ||||
| @@ -274,6 +278,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static ClientComputer getClientComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|   | ||||
| @@ -22,8 +22,50 @@ import java.util.Map; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| /** | ||||
|  * The turtle API allows you to control your turtle. | ||||
|  * Turtles are a robotic device, which can break and place blocks, attack mobs, and move about the world. They have | ||||
|  * an internal inventory of 16 slots, allowing them to store blocks they have broken or would like to place. | ||||
|  * | ||||
|  * ## Movement | ||||
|  * Turtles are capable of moving throug the world. As turtles are blocks themselves, they are confined to Minecraft's | ||||
|  * grid, moving a single block at a time. | ||||
|  * | ||||
|  * {@literal @}{turtle.forward} and @{turtle.back} move the turtle in the direction it is facing, while @{turtle.up} and | ||||
|  * {@literal @}{turtle.down} move it up and down (as one might expect!). In order to move left or right, you first need | ||||
|  * to turn the turtle using @{turtle.turnLeft}/@{turtle.turnRight} and then move forward or backwards. | ||||
|  * | ||||
|  * :::info | ||||
|  * The name "turtle" comes from [Turtle graphics], which originated from the Logo programming language. Here you'd move | ||||
|  * a turtle with various commands like "move 10" and "turn left", much like ComputerCraft's turtles! | ||||
|  * ::: | ||||
|  * | ||||
|  * Moving a turtle (though not turning it) consumes *fuel*. If a turtle does not have any @{turtle.refuel|fuel}, it | ||||
|  * won't move, and the movement functions will return @{false}. If your turtle isn't going anywhere, the first thing to | ||||
|  * check is if you've fuelled your turtle. | ||||
|  * | ||||
|  * :::tip Handling errors | ||||
|  * Many turtle functions can fail in various ways. For instance, a turtle cannot move forward if there's already a block | ||||
|  * there. Instead of erroring, functions which can fail either return @{true} if they succeed, or @{false} and some | ||||
|  * error message if they fail. | ||||
|  * | ||||
|  * Unexpected failures can often lead to strange behaviour. It's often a good idea to check the return values of these | ||||
|  * functions, or wrap them in @{assert} (for instance, use `assert(turtle.forward())` rather than `turtle.forward()`), | ||||
|  * so the program doesn't misbehave. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Turtle upgrades | ||||
|  * While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles | ||||
|  * can be upgraded with *tools* and @{peripheral|peripherals}. Turtles have two upgrade slots, one on the left and right | ||||
|  * sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the @{turtle.equipLeft}/@{turtle.equipRight} | ||||
|  * functions. | ||||
|  * | ||||
|  * Turtle tools allow you to break blocks (@{turtle.dig}) and attack entities (@{turtle.attack}). Some tools are more | ||||
|  * suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more | ||||
|  * damage. Other tools have more niche use-cases, for instance hoes can til dirt. | ||||
|  * | ||||
|  * Peripherals (such as the @{modem|wireless modem} or @{speaker}) can also be equipped as upgrades. These are then | ||||
|  * accessible by accessing the `"left"` or `"right"` peripheral. | ||||
|  * | ||||
|  * [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics" | ||||
|  * @cc.module turtle | ||||
|  * @cc.since 1.3 | ||||
|  */ | ||||
|   | ||||
| @@ -31,7 +31,6 @@ import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.tags.FluidTags; | ||||
| import net.minecraft.world.Container; | ||||
| import net.minecraft.world.entity.Entity; | ||||
| import net.minecraft.world.entity.EntitySelector; | ||||
| import net.minecraft.world.entity.MoverType; | ||||
| import net.minecraft.world.item.DyeColor; | ||||
| import net.minecraft.world.level.Level; | ||||
| @@ -39,6 +38,7 @@ import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.level.material.FluidState; | ||||
| import net.minecraft.world.level.material.PushReaction; | ||||
| import net.minecraft.world.phys.AABB; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| import net.minecraftforge.items.IItemHandlerModifiable; | ||||
| @@ -48,6 +48,7 @@ import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR; | ||||
| import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; | ||||
| @@ -65,6 +66,8 @@ public class TurtleBrain implements ITurtleAccess | ||||
| 
 | ||||
|     private static final int ANIM_DURATION = 8; | ||||
| 
 | ||||
|     public static final Predicate<Entity> PUSHABLE_ENTITY = entity -> !entity.isSpectator() && entity.getPistonPushReaction() != PushReaction.IGNORE; | ||||
| 
 | ||||
|     private TileTurtle owner; | ||||
|     private ComputerProxy proxy; | ||||
|     private GameProfile owningPlayer; | ||||
| @@ -886,7 +889,7 @@ public class TurtleBrain implements ITurtleAccess | ||||
|                     } | ||||
| 
 | ||||
|                     AABB aabb = new AABB( minX, minY, minZ, maxX, maxY, maxZ ); | ||||
|                     List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, EntitySelector.NO_SPECTATORS ); | ||||
|                     List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, PUSHABLE_ENTITY ); | ||||
|                     if( !list.isEmpty() ) | ||||
|                     { | ||||
|                         double pushStep = 1.0f / ANIM_DURATION; | ||||
|   | ||||
| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  * This file is part of ComputerCraft - http://www.computercraft.info | ||||
|  * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. | ||||
|  * Send enquiries to dratcliffe@gmail.com | ||||
|  */ | ||||
| package dan200.computercraft.shared.util; | ||||
| 
 | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraftforge.api.distmarker.Dist; | ||||
| import net.minecraftforge.event.TickEvent; | ||||
| import net.minecraftforge.eventbus.api.SubscribeEvent; | ||||
| import net.minecraftforge.fml.common.Mod; | ||||
| 
 | ||||
| /** | ||||
|  * A monotonically increasing clock which accounts for the game being paused. | ||||
|  */ | ||||
| @Mod.EventBusSubscriber( Dist.CLIENT ) | ||||
| public final class PauseAwareTimer | ||||
| { | ||||
|     private static boolean paused; | ||||
|     private static long pauseTime; | ||||
|     private static long pauseOffset; | ||||
| 
 | ||||
|     private PauseAwareTimer() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public static long getTime() | ||||
|     { | ||||
|         return (paused ? pauseTime : Util.getNanos()) - pauseOffset; | ||||
|     } | ||||
| 
 | ||||
|     @SubscribeEvent | ||||
|     public static void tick( TickEvent.RenderTickEvent event ) | ||||
|     { | ||||
|         if( event.phase != TickEvent.Phase.START ) return; | ||||
| 
 | ||||
|         boolean isPaused = Minecraft.getInstance().isPaused(); | ||||
|         if( isPaused == paused ) return; | ||||
| 
 | ||||
|         if( isPaused ) | ||||
|         { | ||||
|             pauseTime = Util.getNanos(); | ||||
|             paused = true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             pauseOffset += Util.getNanos() - pauseTime; | ||||
|             paused = false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -7,3 +7,7 @@ public net.minecraft.client.gui.components.ChatComponent m_93787_(Lnet/minecraft | ||||
| public net.minecraft.client.gui.components.ChatComponent m_93803_(I)V # removeById | ||||
| # NoTermComputerScreen | ||||
| public net.minecraft.client.Minecraft f_91080_ # screen | ||||
|  | ||||
| # SpeakerInstance/SpeakerManager | ||||
| public com.mojang.blaze3d.audio.Channel m_83652_(I)V # pumpBuffers | ||||
| public net.minecraft.client.sounds.SoundEngine f_120223_ # executor | ||||
|   | ||||
| @@ -1,16 +1,18 @@ | ||||
| --- The Disk API allows you to interact with disk drives. | ||||
| -- | ||||
| -- These functions can operate on locally attached or remote disk drives. To use | ||||
| -- a locally attached drive, specify “side” as one of the six sides | ||||
| -- (e.g. `left`); to use a remote disk drive, specify its name as printed when | ||||
| -- enabling its modem (e.g. `drive_0`). | ||||
| -- | ||||
| -- **Note:** All computers (except command computers), turtles and pocket | ||||
| -- computers can be placed within a disk drive to access it's internal storage | ||||
| -- like a disk. | ||||
| -- | ||||
| -- @module disk | ||||
| -- @since 1.2 | ||||
| --[[- The Disk API allows you to interact with disk drives. | ||||
|  | ||||
| These functions can operate on locally attached or remote disk drives. To use a | ||||
| locally attached drive, specify “side” as one of the six sides (e.g. `left`); to | ||||
| use a remote disk drive, specify its name as printed when enabling its modem | ||||
| (e.g. `drive_0`). | ||||
|  | ||||
| :::tip | ||||
| All computers (except command computers), turtles and pocket computers can be | ||||
| placed within a disk drive to access it's internal storage like a disk. | ||||
| ::: | ||||
|  | ||||
| @module disk | ||||
| @since 1.2 | ||||
| ]] | ||||
|  | ||||
| local function isDrive(name) | ||||
|     if type(name) ~= "string" then | ||||
|   | ||||
| @@ -1,27 +1,30 @@ | ||||
| --- The GPS API provides a method for turtles and computers to retrieve their | ||||
| -- own locations. | ||||
| -- | ||||
| -- It broadcasts a PING message over @{rednet} and wait for responses. In order | ||||
| -- for this system to work, there must be at least 4 computers used as gps hosts | ||||
| -- which will respond and allow trilateration. Three of these hosts should be in | ||||
| -- a plane, and the fourth should be either above or below the other three. The | ||||
| -- three in a plane should not be in a line with each other. You can set up | ||||
| -- hosts using the gps program. | ||||
| -- | ||||
| -- **Note**: When entering in the coordinates for the host you need to put in | ||||
| -- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all | ||||
| -- rednet distances are measured from the block the computer is in. | ||||
| -- | ||||
| -- Also note that you may choose which axes x, y, or z refers to - so long as | ||||
| -- your systems have the same definition as any GPS servers that're in range, it | ||||
| -- works just the same. For example, you might build a GPS cluster according to | ||||
| -- [this tutorial][1], using z to account for height, or you might use y to | ||||
| -- account for height in the way that Minecraft's debug screen displays. | ||||
| -- | ||||
| -- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/ | ||||
| -- | ||||
| -- @module gps | ||||
| -- @since 1.31 | ||||
| --[[- The GPS API provides a method for turtles and computers to retrieve their | ||||
| own locations. | ||||
|  | ||||
| It broadcasts a PING message over @{rednet} and wait for responses. In order for | ||||
| this system to work, there must be at least 4 computers used as gps hosts which | ||||
| will respond and allow trilateration. Three of these hosts should be in a plane, | ||||
| and the fourth should be either above or below the other three. The three in a | ||||
| plane should not be in a line with each other. You can set up hosts using the | ||||
| gps program. | ||||
|  | ||||
| :::note | ||||
| When entering in the coordinates for the host you need to put in the `x`, `y`, | ||||
| and `z` coordinates of the computer, not the modem, as all modem distances are | ||||
| measured from the block the computer is in. | ||||
| ::: | ||||
|  | ||||
| Also note that you may choose which axes x, y, or z refers to - so long as your | ||||
| systems have the same definition as any GPS servers that're in range, it works | ||||
| just the same. For example, you might build a GPS cluster according to [this | ||||
| tutorial][1], using z to account for height, or you might use y to account for | ||||
| height in the way that Minecraft's debug screen displays. | ||||
|  | ||||
| [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/ | ||||
|  | ||||
| @module gps | ||||
| @since 1.31 | ||||
| ]] | ||||
|  | ||||
| local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||
|  | ||||
|   | ||||
| @@ -2,13 +2,13 @@ | ||||
|  | ||||
| Functions are not actually executed simultaniously, but rather this API will | ||||
| automatically switch between them whenever they yield (eg whenever they call | ||||
| @{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or | ||||
| @{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or | ||||
| functions that call that, etc - basically, anything that causes the function | ||||
| to "pause"). | ||||
|  | ||||
| Each function executed in "parallel" gets its own copy of the event queue, | ||||
| and so "event consuming" functions (again, mostly anything that causes the | ||||
| script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API, | ||||
| script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API, | ||||
| etc) can safely be used in one without affecting the event queue accessed by | ||||
| the other. | ||||
|  | ||||
|   | ||||
| @@ -66,7 +66,7 @@ type. | ||||
| What is a peripheral type though? This is a string which describes what a | ||||
| peripheral is, and so what functions are available on it. For instance, speakers | ||||
| are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might | ||||
| have more than one type; a Minecraft chest is both a `"minecraft:chest"` and | ||||
| have more than one type - a Minecraft chest is both a `"minecraft:chest"` and | ||||
| `"inventory"`. | ||||
|  | ||||
| You can get all the types a peripheral has with @{peripheral.getType}, and check | ||||
|   | ||||
| @@ -1,21 +1,48 @@ | ||||
| --- The Rednet API allows systems to communicate between each other without | ||||
| -- using redstone. It serves as a wrapper for the modem API, offering ease of | ||||
| -- functionality (particularly in regards to repeating signals) with some | ||||
| -- expense of fine control. | ||||
| -- | ||||
| -- In order to send and receive data, a modem (either wired, wireless, or ender) | ||||
| -- is required. The data reaches any possible destinations immediately after | ||||
| -- sending it, but is range limited. | ||||
| -- | ||||
| -- Rednet also allows you to use a "protocol" - simple string names indicating | ||||
| -- what messages are about. Receiving systems may filter messages according to | ||||
| -- their protocols, thereby automatically ignoring incoming messages which don't | ||||
| -- specify an identical string. It's also possible to @{rednet.lookup|lookup} | ||||
| -- which systems in the area use certain protocols, hence making it easier to | ||||
| -- determine where given messages should be sent in the first place. | ||||
| -- | ||||
| -- @module rednet | ||||
| -- @since 1.2 | ||||
| --[[- The Rednet API allows computers to communicate between each other by using | ||||
| @{modem|modems}. It provides a layer of abstraction on top of the main @{modem} | ||||
| peripheral, making it slightly easier to use. | ||||
|  | ||||
| ## Basic usage | ||||
| In order to send a message between two computers, each computer must have a | ||||
| modem on one of its sides (or in the case of pocket computers and turtles, the | ||||
| modem must be equipped as an upgrade). The two computers should then call | ||||
| @{rednet.open}, which sets up the modems ready to send and receive messages. | ||||
|  | ||||
| Once rednet is opened, you can send messages using @{rednet.send} and receive | ||||
| them using @{rednet.receive}. It's also possible to send a message to _every_ | ||||
| rednet-using computer using @{rednet.broadcast}. | ||||
|  | ||||
| :::caution Network security | ||||
|  | ||||
| While rednet provides a friendly way to send messages to specific computers, it | ||||
| doesn't provide any guarantees about security. Other computers could be | ||||
| listening in to your messages, or even pretending to send messages from other computers! | ||||
|  | ||||
| If you're playing on a multi-player server (or at least one where you don't | ||||
| trust other players), it's worth encrypting or signing your rednet messages. | ||||
| ::: | ||||
|  | ||||
| ## Protocols and hostnames | ||||
| Several rednet messages accept "protocol"s - simple string names describing what | ||||
| a message is about. When sending messages using @{rednet.send} and | ||||
| @{rednet.broadcast}, you can optionally specify a protocol for the message. This | ||||
| same protocol can then be given to @{rednet.receive}, to ignore all messages not | ||||
| using this protocol. | ||||
|  | ||||
| It's also possible to look-up computers based on protocols, providing a basic | ||||
| system for service discovery and [DNS]. A computer can advertise that it | ||||
| supports a particular protocol with @{rednet.host}, also providing a friendly | ||||
| "hostname". Other computers may then find all computers which support this | ||||
| protocol using @{rednet.lookup}. | ||||
|  | ||||
| [DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System" | ||||
|  | ||||
| @module rednet | ||||
| @since 1.2 | ||||
| @see rednet_message Queued when a rednet message is received. | ||||
| @see modem Rednet is built on top of the modem peripheral. Modems provide a more | ||||
| bare-bones but flexible interface. | ||||
| ]] | ||||
|  | ||||
| local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||
|  | ||||
| @@ -46,9 +73,17 @@ This will open the modem on two channels: one which has the same | ||||
|  | ||||
| @tparam string modem The name of the modem to open. | ||||
| @throws If there is no such modem with the given name | ||||
| @usage Open a wireless modem on the back of the computer. | ||||
| @usage Open rednet on the back of the computer, allowing you to send and receive | ||||
| rednet messages using it. | ||||
|  | ||||
|     rednet.open("back") | ||||
|  | ||||
| @usage Open rednet on all attached modems. This abuses the "filter" argument to | ||||
| @{peripheral.find}. | ||||
|  | ||||
|     peripheral.find("modem", rednet.open) | ||||
| @see rednet.close | ||||
| @see rednet.isOpen | ||||
| ]] | ||||
| function open(modem) | ||||
|     expect(1, modem, "string") | ||||
| @@ -65,6 +100,7 @@ end | ||||
| -- @tparam[opt] string modem The side the modem exists on. If not given, all | ||||
| -- open modems will be closed. | ||||
| -- @throws If there is no such modem with the given name | ||||
| -- @see rednet.open | ||||
| function close(modem) | ||||
|     expect(1, modem, "string", "nil") | ||||
|     if modem then | ||||
| @@ -90,6 +126,7 @@ end | ||||
| -- modems will be checked. | ||||
| -- @treturn boolean If the given modem is open. | ||||
| -- @since 1.31 | ||||
| -- @see rednet.open | ||||
| function isOpen(modem) | ||||
|     expect(1, modem, "string", "nil") | ||||
|     if modem then | ||||
| @@ -109,15 +146,17 @@ function isOpen(modem) | ||||
| end | ||||
|  | ||||
| --[[- Allows a computer or turtle with an attached modem to send a message | ||||
| intended for a system with a specific ID. At least one such modem must first | ||||
| intended for a sycomputer with a specific ID. At least one such modem must first | ||||
| be @{rednet.open|opened} before sending is possible. | ||||
|  | ||||
| Assuming the target was in range and also had a correctly opened modem, it | ||||
| may then use @{rednet.receive} to collect the message. | ||||
| Assuming the target was in range and also had a correctly opened modem, the | ||||
| target computer may then use @{rednet.receive} to collect the message. | ||||
|  | ||||
| @tparam number recipient The ID of the receiving computer. | ||||
| @param message The message to send. This should not contain coroutines or | ||||
| functions, as they will be converted to @{nil}. | ||||
| @param message The message to send. Like with @{modem.transmit}, this can | ||||
| contain any primitive type (numbers, booleans and strings) as well as | ||||
| tables. Other types (like functions), as well as metatables, will not be | ||||
| transmitted. | ||||
| @tparam[opt] string protocol The "protocol" to send this message under. When | ||||
| using @{rednet.receive} one can filter to only receive messages sent under a | ||||
| particular protocol. | ||||
| @@ -174,16 +213,19 @@ function send(recipient, message, protocol) | ||||
|     return sent | ||||
| end | ||||
|  | ||||
| --- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} | ||||
| -- channel. The message will be received by every device listening to rednet. | ||||
| -- | ||||
| -- @param message The message to send. This should not contain coroutines or | ||||
| -- functions, as they will be converted to @{nil}. | ||||
| -- @tparam[opt] string protocol The "protocol" to send this message under. When | ||||
| -- using @{rednet.receive} one can filter to only receive messages sent under a | ||||
| -- particular protocol. | ||||
| -- @see rednet.receive | ||||
| -- @changed 1.6 Added protocol parameter. | ||||
| --[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} | ||||
| channel. The message will be received by every device listening to rednet. | ||||
|  | ||||
| @param message The message to send. This should not contain coroutines or | ||||
| functions, as they will be converted to @{nil}.  @tparam[opt] string protocol | ||||
| The "protocol" to send this message under. When using @{rednet.receive} one can | ||||
| filter to only receive messages sent under a particular protocol. | ||||
| @see rednet.receive | ||||
| @changed 1.6 Added protocol parameter. | ||||
| @usage Broadcast the words "Hello, world!" to every computer using rednet. | ||||
|  | ||||
|     rednet.broadcast("Hello, world!") | ||||
| ]] | ||||
| function broadcast(message, protocol) | ||||
|     expect(2, protocol, "string", "nil") | ||||
|     send(CHANNEL_BROADCAST, message, protocol) | ||||
| @@ -263,25 +305,25 @@ function receive(protocol_filter, timeout) | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Register the system as "hosting" the desired protocol under the specified | ||||
| -- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and | ||||
| -- maybe name) on the same network, the registered system will automatically | ||||
| -- respond via a background process, hence providing the system performing the | ||||
| -- lookup with its ID number. | ||||
| -- | ||||
| -- Multiple computers may not register themselves on the same network as having | ||||
| -- the same names against the same protocols, and the title `localhost` is | ||||
| -- specifically reserved. They may, however, share names as long as their hosted | ||||
| -- protocols are different, or if they only join a given network after | ||||
| -- "registering" themselves before doing so (eg while offline or part of a | ||||
| -- different network). | ||||
| -- | ||||
| -- @tparam string protocol The protocol this computer provides. | ||||
| -- @tparam string hostname The name this protocol exposes for the given protocol. | ||||
| -- @throws If trying to register a hostname which is reserved, or currently in use. | ||||
| -- @see rednet.unhost | ||||
| -- @see rednet.lookup | ||||
| -- @since 1.6 | ||||
| --[[- Register the system as "hosting" the desired protocol under the specified | ||||
| name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and | ||||
| maybe name) on the same network, the registered system will automatically | ||||
| respond via a background process, hence providing the system performing the | ||||
| lookup with its ID number. | ||||
|  | ||||
| Multiple computers may not register themselves on the same network as having the | ||||
| same names against the same protocols, and the title `localhost` is specifically | ||||
| reserved. They may, however, share names as long as their hosted protocols are | ||||
| different, or if they only join a given network after "registering" themselves | ||||
| before doing so (eg while offline or part of a different network). | ||||
|  | ||||
| @tparam string protocol The protocol this computer provides. | ||||
| @tparam string hostname The name this protocol exposes for the given protocol. | ||||
| @throws If trying to register a hostname which is reserved, or currently in use. | ||||
| @see rednet.unhost | ||||
| @see rednet.lookup | ||||
| @since 1.6 | ||||
| ]] | ||||
| function host(protocol, hostname) | ||||
|     expect(1, protocol, "string") | ||||
|     expect(2, hostname, "string") | ||||
| @@ -306,21 +348,38 @@ function unhost(protocol) | ||||
|     hostnames[protocol] = nil | ||||
| end | ||||
|  | ||||
| --- Search the local rednet network for systems @{rednet.host|hosting} the | ||||
| -- desired protocol and returns any computer IDs that respond as "registered" | ||||
| -- against it. | ||||
| -- | ||||
| -- If a hostname is specified, only one ID will be returned (assuming an exact | ||||
| -- match is found). | ||||
| -- | ||||
| -- @tparam string protocol The protocol to search for. | ||||
| -- @tparam[opt] string hostname The hostname to search for. | ||||
| -- | ||||
| -- @treturn[1] { number }|nil A list of computer IDs hosting the given | ||||
| -- protocol, or @{nil} if none exist. | ||||
| -- @treturn[2] number|nil The computer ID with the provided hostname and protocol, | ||||
| -- or @{nil} if none exists. | ||||
| -- @since 1.6 | ||||
| --[[- Search the local rednet network for systems @{rednet.host|hosting} the | ||||
| desired protocol and returns any computer IDs that respond as "registered" | ||||
| against it. | ||||
|  | ||||
| If a hostname is specified, only one ID will be returned (assuming an exact | ||||
| match is found). | ||||
|  | ||||
| @tparam string protocol The protocol to search for. | ||||
| @tparam[opt] string hostname The hostname to search for. | ||||
|  | ||||
| @treturn[1] number... A list of computer IDs hosting the given protocol. | ||||
| @treturn[2] number|nil The computer ID with the provided hostname and protocol, | ||||
| or @{nil} if none exists. | ||||
| @since 1.6 | ||||
| @usage Find all computers which are hosting the `"chat"` protocol. | ||||
|  | ||||
|     local computers = {rednet.lookup("chat")} | ||||
|     print(#computers .. " computers available to chat") | ||||
|     for _, computer in pairs(computers) do | ||||
|       print("Computer #" .. computer) | ||||
|     end | ||||
|  | ||||
| @usage Find a computer hosting the `"chat"` protocol with a hostname of `"my_host"`. | ||||
|  | ||||
|     local id = rednet.lookup("chat", "my_host") | ||||
|     if id then | ||||
|       print("Found my_host at computer #" .. id) | ||||
|     else | ||||
|       printError("Cannot find my_host") | ||||
|     end | ||||
|  | ||||
| ]] | ||||
| function lookup(protocol, hostname) | ||||
|     expect(1, protocol, "string") | ||||
|     expect(2, hostname, "string", "nil") | ||||
|   | ||||
| @@ -108,39 +108,48 @@ local function makePagedScroll(_term, _nFreeLines) | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Prints a given string to the display. | ||||
| -- | ||||
| -- If the action can be completed without scrolling, it acts much the same as | ||||
| -- @{print}; otherwise, it will throw up a "Press any key to continue" prompt at | ||||
| -- the bottom of the display. Each press will cause it to scroll down and write | ||||
| -- a single line more before prompting again, if need be. | ||||
| -- | ||||
| -- @tparam string _sText The text to print to the screen. | ||||
| -- @tparam[opt] number _nFreeLines The number of lines which will be | ||||
| -- automatically scrolled before the first prompt appears (meaning _nFreeLines + | ||||
| -- 1 lines will be printed). This can be set to the terminal's height - 2 to | ||||
| -- always try to fill the screen. Defaults to 0, meaning only one line is | ||||
| -- displayed before prompting. | ||||
| -- @treturn number The number of lines printed. | ||||
| -- @usage | ||||
| --     local width, height = term.getSize() | ||||
| --     textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2) | ||||
| function pagedPrint(_sText, _nFreeLines) | ||||
|     expect(2, _nFreeLines, "number", "nil") | ||||
| --[[- Prints a given string to the display. | ||||
|  | ||||
| If the action can be completed without scrolling, it acts much the same as | ||||
| @{print}; otherwise, it will throw up a "Press any key to continue" prompt at | ||||
| the bottom of the display. Each press will cause it to scroll down and write a | ||||
| single line more before prompting again, if need be. | ||||
|  | ||||
| @tparam string text The text to print to the screen. | ||||
| @tparam[opt] number free_lines The number of lines which will be | ||||
| automatically scrolled before the first prompt appears (meaning free_lines + | ||||
| 1 lines will be printed). This can be set to the cursor's y position - 2 to | ||||
| always try to fill the screen. Defaults to 0, meaning only one line is | ||||
| displayed before prompting. | ||||
| @treturn number The number of lines printed. | ||||
|  | ||||
| @usage Generates several lines of text and then prints it, paging once the | ||||
| bottom of the terminal is reached. | ||||
|  | ||||
|     local lines = {} | ||||
|     for i = 1, 30 do lines[i] = ("This is line #%d"):format(i) end | ||||
|     local message = table.concat(lines, "\n") | ||||
|  | ||||
|     local width, height = term.getCursorPos() | ||||
|     textutils.pagedPrint(message, height - 2) | ||||
| ]] | ||||
| function pagedPrint(text, free_lines) | ||||
|     expect(2, free_lines, "number", "nil") | ||||
|     -- Setup a redirector | ||||
|     local oldTerm = term.current() | ||||
|     local newTerm = {} | ||||
|     for k, v in pairs(oldTerm) do | ||||
|         newTerm[k] = v | ||||
|     end | ||||
|     newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines) | ||||
|  | ||||
|     newTerm.scroll = makePagedScroll(oldTerm, free_lines) | ||||
|     term.redirect(newTerm) | ||||
|  | ||||
|     -- Print the text | ||||
|     local result | ||||
|     local ok, err = pcall(function() | ||||
|         if _sText ~= nil then | ||||
|             result = print(_sText) | ||||
|         if text ~= nil then | ||||
|             result = print(text) | ||||
|         else | ||||
|             result = print() | ||||
|         end | ||||
| @@ -214,32 +223,45 @@ local function tabulateCommon(bPaged, ...) | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Prints tables in a structured form. | ||||
| -- | ||||
| -- This accepts multiple arguments, either a table or a number. When | ||||
| -- encountering a table, this will be treated as a table row, with each column | ||||
| -- width being auto-adjusted. | ||||
| -- | ||||
| -- When encountering a number, this sets the text color of the subsequent rows to it. | ||||
| -- | ||||
| -- @tparam {string...}|number ... The rows and text colors to display. | ||||
| -- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" }) | ||||
| -- @since 1.3 | ||||
| --[[- Prints tables in a structured form. | ||||
|  | ||||
| This accepts multiple arguments, either a table or a number. When | ||||
| encountering a table, this will be treated as a table row, with each column | ||||
| width being auto-adjusted. | ||||
|  | ||||
| When encountering a number, this sets the text color of the subsequent rows to it. | ||||
|  | ||||
| @tparam {string...}|number ... The rows and text colors to display. | ||||
| @since 1.3 | ||||
| @usage | ||||
|  | ||||
|     textutils.tabulate( | ||||
|       colors.orange, { "1", "2", "3" }, | ||||
|       colors.lightBlue, { "A", "B", "C" } | ||||
|     ) | ||||
| ]] | ||||
| function tabulate(...) | ||||
|     return tabulateCommon(false, ...) | ||||
| end | ||||
|  | ||||
| --- Prints tables in a structured form, stopping and prompting for input should | ||||
| -- the result not fit on the terminal. | ||||
| -- | ||||
| -- This functions identically to @{textutils.tabulate}, but will prompt for user | ||||
| -- input should the whole output not fit on the display. | ||||
| -- | ||||
| -- @tparam {string...}|number ... The rows and text colors to display. | ||||
| -- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" }) | ||||
| -- @see textutils.tabulate | ||||
| -- @see textutils.pagedPrint | ||||
| -- @since 1.3 | ||||
| --[[- Prints tables in a structured form, stopping and prompting for input should | ||||
| the result not fit on the terminal. | ||||
|  | ||||
| This functions identically to @{textutils.tabulate}, but will prompt for user | ||||
| input should the whole output not fit on the display. | ||||
|  | ||||
| @tparam {string...}|number ... The rows and text colors to display. | ||||
| @see textutils.tabulate | ||||
| @see textutils.pagedPrint | ||||
| @since 1.3 | ||||
|  | ||||
| @usage Generates a long table, tabulates it, and prints it to the screen. | ||||
|  | ||||
|     local rows = {} | ||||
|     for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end | ||||
|  | ||||
|     textutils.tabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows)) | ||||
| ]] | ||||
| function pagedTabulate(...) | ||||
|     return tabulateCommon(true, ...) | ||||
| end | ||||
| @@ -692,11 +714,11 @@ saving in a file or pretty-printing. | ||||
| @throws If the object contains a value which cannot be | ||||
| serialised. This includes functions and tables which appear multiple | ||||
| times. | ||||
| @see cc.pretty.pretty An alternative way to display a table, often more suitable for | ||||
| pretty printing. | ||||
| @see cc.pretty.pretty_print An alternative way to display a table, often more | ||||
| suitable for pretty printing. | ||||
| @since 1.3 | ||||
| @changed 1.97.0 Added `opts` argument. | ||||
| @usage Pretty print a basic table. | ||||
| @usage Serialise a basic table. | ||||
|  | ||||
|     textutils.serialise({ 1, 2, 3, a = 1, ["another key"] = { true } }) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| --- The turtle API allows you to control your turtle. | ||||
| -- | ||||
| -- @module turtle | ||||
| --- @module turtle | ||||
|  | ||||
| if not turtle then | ||||
|     error("Cannot load turtle API on computer", 2) | ||||
| @@ -8,9 +6,8 @@ end | ||||
|  | ||||
| --- The builtin turtle API, without any generated helper functions. | ||||
| -- | ||||
| -- Generally you should not need to use this table - it only exists for | ||||
| -- backwards compatibility reasons. | ||||
| -- @deprecated | ||||
| -- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the base. You | ||||
| -- should not need to use it. | ||||
| native = turtle.native or turtle | ||||
|  | ||||
| local function addCraftMethod(object) | ||||
|   | ||||
| @@ -1,3 +1,15 @@ | ||||
| # New features in CC: Tweaked 1.100.0 | ||||
| 
 | ||||
| * Speakers can now play arbitrary PCM audio. | ||||
| * Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module. | ||||
| * Wired modems now only render breaking progress for the part which is being broken. | ||||
| * Various documentation improvements. | ||||
| 
 | ||||
| Several bug fixes: | ||||
| * Fix the "repeat" program not repeating broadcast rednet messages. | ||||
| * Fix the drag-and-drop upload functionality writing empty files. | ||||
| * Prevent turtles from pushing non-pushable entities. | ||||
| 
 | ||||
| # New features in CC: Tweaked 1.99.1 | ||||
| 
 | ||||
| * Add package.searchpath to the cc.require API. (MCJack123) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ Uses LuaJ from http://luaj.sourceforge.net/ | ||||
| The ComputerCraft 1.76 update was sponsored by MinecraftU and Deep Space. | ||||
| Visit http://www.minecraftu.org and http://www.deepspace.me/space-cadets to find out more. | ||||
|  | ||||
| Join the ComputerCraft community online at http://www.computercraft.info | ||||
| Join the ComputerCraft community online at https://computercraft.cc | ||||
| Follow @DanTwoHundred on Twitter! | ||||
|  | ||||
| To help contribute to ComputerCraft, browse the source code at https://github.com/dan200/ComputerCraft. | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| New features in CC: Tweaked 1.99.1 | ||||
| New features in CC: Tweaked 1.100.0 | ||||
| 
 | ||||
| * Add package.searchpath to the cc.require API. (MCJack123) | ||||
| * Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases. | ||||
| * Speakers can now play arbitrary PCM audio. | ||||
| * Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module. | ||||
| * Wired modems now only render breaking progress for the part which is being broken. | ||||
| * Various documentation improvements. | ||||
| 
 | ||||
| Several bug fixes: | ||||
| * Fix keys being "sticky" when opening the off-hand pocket computer GUI. | ||||
| * Correctly handle broken coroutine managers resuming Java code with a `nil` event. | ||||
| * Prevent computer buttons stealing focus from the terminal. | ||||
| * Fix a class cast exception when a monitor is malformed in ways I do not quite understand. | ||||
| * Fix the "repeat" program not repeating broadcast rednet messages. | ||||
| * Fix the drag-and-drop upload functionality writing empty files. | ||||
| * Prevent turtles from pushing non-pushable entities. | ||||
| 
 | ||||
| Type "help changelog" to see the full version history. | ||||
|   | ||||
| @@ -36,7 +36,7 @@ local encoder = dfpwm.make_encoder() | ||||
| local decoder = dfpwm.make_decoder() | ||||
|  | ||||
| local out = fs.open("speedy.dfpwm", "wb") | ||||
| for input in io.lines("my_audio_track.dfpwm", 16 * 1024 * 2) do | ||||
| for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do | ||||
|   local decoded = decoder(input) | ||||
|   local output = {} | ||||
|  | ||||
|   | ||||
| @@ -225,7 +225,7 @@ end | ||||
| --- Display a document on the terminal. | ||||
| -- | ||||
| -- @tparam          Doc     doc         The document to render | ||||
| -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| -- @tparam[opt=0.6] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| local function write(doc, ribbon_frac) | ||||
|     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end | ||||
|     expect(2, ribbon_frac, "number", "nil") | ||||
| @@ -287,7 +287,7 @@ end | ||||
| --- Display a document on the terminal with a trailing new line. | ||||
| -- | ||||
| -- @tparam          Doc     doc         The document to render. | ||||
| -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| -- @tparam[opt=0.6] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| local function print(doc, ribbon_frac) | ||||
|     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end | ||||
|     expect(2, ribbon_frac, "number", "nil") | ||||
| @@ -298,9 +298,9 @@ end | ||||
| --- Render a document, converting it into a string. | ||||
| -- | ||||
| -- @tparam          Doc     doc         The document to render. | ||||
| -- @tparam[opt] number  width       The maximum width of this document. Note that long strings will not be wrapped to | ||||
| -- fit this width - it is only used for finding the best layout. | ||||
| -- @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| -- @tparam[opt]     number width The maximum width of this document. Note that long strings will not be wrapped to fit | ||||
| -- this width - it is only used for finding the best layout. | ||||
| -- @tparam[opt=0.6] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| -- @treturn string The rendered document as a string. | ||||
| local function render(doc, width, ribbon_frac) | ||||
|     if getmetatable(doc) ~= Doc then expect(1, doc, "document") end | ||||
| @@ -483,7 +483,7 @@ Controls how various properties are displayed. | ||||
|  - `function_args`: Show the arguments to a function if known (`false` by default). | ||||
|  - `function_source`: Show where the function was defined, instead of | ||||
|    `function: xxxxxxxx` (`false` by default). | ||||
| @tparam[opt] number  ribbon_frac The maximum fraction of the width that we should write in. | ||||
| @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in. | ||||
|  | ||||
| @usage Display a table on the screen. | ||||
|  | ||||
|   | ||||
| @@ -1,22 +1,24 @@ | ||||
| --- This provides a pure Lua implementation of the builtin @{require} function | ||||
| -- and @{package} library. | ||||
| -- | ||||
| -- Generally you do not need to use this module - it is injected into the | ||||
| -- every program's environment. However, it may be useful when building a | ||||
| -- custom shell or when running programs yourself. | ||||
| -- | ||||
| -- @module cc.require | ||||
| -- @since 1.88.0 | ||||
| -- @usage Construct the package and require function, and insert them into a | ||||
| -- custom environment. | ||||
| -- | ||||
| --     local r = require "cc.require" | ||||
| --     local env = setmetatable({}, { __index = _ENV }) | ||||
| --     env.require, env.package = r.make(env, "/") | ||||
| -- | ||||
| --     -- Now we have our own require function, separate to the original. | ||||
| --     local r2 = env.require "cc.require" | ||||
| --     print(r, r2) | ||||
| --[[- This provides a pure Lua implementation of the builtin @{require} function | ||||
| and @{package} library. | ||||
|  | ||||
| Generally you do not need to use this module - it is injected into the every | ||||
| program's environment. However, it may be useful when building a custom shell or | ||||
| when running programs yourself. | ||||
|  | ||||
| @module cc.require | ||||
| @since 1.88.0 | ||||
| @see using_require For an introduction on how to use @{require}. | ||||
| @usage Construct the package and require function, and insert them into a | ||||
| custom environment. | ||||
|  | ||||
|     local r = require "cc.require" | ||||
|     local env = setmetatable({}, { __index = _ENV }) | ||||
|     env.require, env.package = r.make(env, "/") | ||||
|  | ||||
|     -- Now we have our own require function, separate to the original. | ||||
|     local r2 = env.require "cc.require" | ||||
|     print(r, r2) | ||||
| ]] | ||||
|  | ||||
| local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua") | ||||
| local expect = expect.expect | ||||
|   | ||||
| @@ -171,7 +171,7 @@ end | ||||
| local current_section = nil | ||||
| local offset = 0 | ||||
|  | ||||
| --- Find the currently visible seciton, or nil if this document has no sections. | ||||
| --- Find the currently visible section, or nil if this document has no sections. | ||||
| -- | ||||
| -- This could potentially be a binary search, but right now it's not worth it. | ||||
| local function find_section() | ||||
|   | ||||
| @@ -26,13 +26,14 @@ const Click = (options: { run: () => void }) => | ||||
|  | ||||
| type WindowProps = {}; | ||||
|  | ||||
| type WindowState = { | ||||
|     visible: boolean, | ||||
|  | ||||
|     example: string, | ||||
|     exampleIdx: number, | ||||
| type Example = { | ||||
|     files: { [file: string]: string }, | ||||
| } | ||||
|  | ||||
| type WindowState = { | ||||
|     exampleIdx: number, | ||||
| } & ({ visible: false, example: null } | { visible: true, example: Example }) | ||||
|  | ||||
| type Touch = { clientX: number, clientY: number }; | ||||
|  | ||||
| class Window extends Component<WindowProps, WindowState> { | ||||
| @@ -41,12 +42,14 @@ class Window extends Component<WindowProps, WindowState> { | ||||
|     private top: number = 0; | ||||
|     private dragging?: { downX: number, downY: number, initialX: number, initialY: number }; | ||||
|  | ||||
|     private snippets: { [file: string]: string } = {}; | ||||
|  | ||||
|     constructor(props: WindowProps, context: unknown) { | ||||
|         super(props, context); | ||||
|  | ||||
|         this.state = { | ||||
|             visible: false, | ||||
|             example: "", | ||||
|             example: null, | ||||
|             exampleIdx: 0, | ||||
|         } | ||||
|     } | ||||
| @@ -57,10 +60,16 @@ class Window extends Component<WindowProps, WindowState> { | ||||
|             const element = elements[i] as HTMLElement; | ||||
|  | ||||
|             let example = element.innerText; | ||||
|  | ||||
|             const snippet = element.getAttribute("data-snippet"); | ||||
|             if (snippet) this.snippets[snippet] = example; | ||||
|  | ||||
|             if (element.getAttribute("data-lua-kind") == "expr") { | ||||
|                 example = exprTemplate.replace("__expr__", example); | ||||
|             } | ||||
|             render(<Click run={this.runExample(example)} />, element); | ||||
|  | ||||
|             const mount = element.getAttribute("data-mount"); | ||||
|             render(<Click run={this.runExample(example, mount)} />, element); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -76,13 +85,13 @@ class Window extends Component<WindowProps, WindowState> { | ||||
|             </div> | ||||
|             <div class="computer-container"> | ||||
|                 <Computer key={exampleIdx} files={{ | ||||
|                     "example.lua": example, ...defaultFiles | ||||
|                     ...example!.files, ...defaultFiles | ||||
|                 }} /> | ||||
|             </div> | ||||
|         </div> : <div class="example-window example-window-hidden" />; | ||||
|     } | ||||
|  | ||||
|     private runExample(example: string): () => void { | ||||
|     private runExample(example: string, mount: string | null): () => void { | ||||
|         return () => { | ||||
|             if (!this.positioned) { | ||||
|                 this.positioned = true; | ||||
| @@ -90,9 +99,17 @@ class Window extends Component<WindowProps, WindowState> { | ||||
|                 this.top = 20; | ||||
|             } | ||||
|  | ||||
|             const files: { [file: string]: string } = { "example.lua": example }; | ||||
|             if (mount !== null) { | ||||
|                 for (const toMount of mount.split(",")) { | ||||
|                     const [name, path] = toMount.split(":", 2); | ||||
|                     files[path] = this.snippets[name] || ""; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.setState(({ exampleIdx }: WindowState) => ({ | ||||
|                 visible: true, | ||||
|                 example: example, | ||||
|                 example: { files }, | ||||
|                 exampleIdx: exampleIdx + 1, | ||||
|             })); | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates