1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 12:10:30 +00:00

Merge branch 'mc-1.16.x' into mc-1.17.x

This commit is contained in:
Jonathan Coates 2021-12-21 15:10:19 +00:00
commit f794ce42ab
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
37 changed files with 758 additions and 326 deletions

View File

@ -174,7 +174,7 @@ dependencies {
testModImplementation sourceSets.main.output testModImplementation sourceSets.main.output
testModExtra 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21' 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 // Compile tasks

View File

@ -2,7 +2,7 @@
module: [kind=event] modem_message 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 ## Return Values
1. @{string}: The event name. 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. 3. @{number}: The channel that the message was sent on.
4. @{number}: The reply channel set by the sender. 4. @{number}: The reply channel set by the sender.
5. @{any}: The message as sent 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 ## Example
Prints a message when one is sent: Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
```lua ```lua
local modem = peripheral.find("modem") or error("No modem attached", 0)
modem.open(0)
while true do while true do
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") 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))) 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)))

View 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).

View File

@ -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 or the @{parallel|parallel API}, it will only pause execution of the current
thread, not the whole program. thread, not the whole program.
**Note** Because sleep internally uses timers, it is a function that yields. :::tip
This means that you can use it to prevent "Too long without yielding" errors, Because sleep internally uses timers, it is a function that yields. This means
however, as the minimum sleep time is 0.05 seconds, it will slow your program that you can use it to prevent "Too long without yielding" errors, however, as
down. 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 @{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 that any event that occurs while sleeping will be entirely discarded. If you
need to receive events while sleeping, consider using @{os.startTimer|timers}, need to receive events while sleeping, consider using @{os.startTimer|timers},
or the @{parallel|parallel API}. or the @{parallel|parallel API}.
:::
@tparam number time The number of seconds to sleep for, rounded up to the @tparam number time The number of seconds to sleep for, rounded up to the
nearest multiple of 0.05. nearest multiple of 0.05.

View File

@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
# Mod properties # Mod properties
mod_version=1.99.1 mod_version=1.100.0
# Minecraft properties (update mods.toml when changing) # Minecraft properties (update mods.toml when changing)
mc_version=1.17.1 mc_version=1.17.1

View File

@ -5,9 +5,8 @@
*/ */
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map; import java.util.Map;
import static dan200.computercraft.api.lua.LuaValues.*; import static dan200.computercraft.api.lua.LuaValues.*;

View File

@ -115,7 +115,9 @@ class DfpwmStream implements AudioStream
} }
result.flip(); 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 @Override
@ -123,4 +125,9 @@ class DfpwmStream implements AudioStream
{ {
buffers.clear(); buffers.clear();
} }
public boolean isEmpty()
{
return buffers.isEmpty();
}
} }

View File

@ -27,8 +27,20 @@ public class SpeakerInstance
public synchronized void pushAudio( ByteBuf buffer ) 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 ); 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 ) public void playAudio( Vec3 position, float volume )

View File

@ -26,12 +26,14 @@ public class SpeakerManager
@SubscribeEvent @SubscribeEvent
public static void playStreaming( PlayStreamingSourceEvent event ) public static void playStreaming( PlayStreamingSourceEvent event )
{ {
if( !(event.getSound() instanceof SpeakerSound) ) return; if( !(event.getSound() instanceof SpeakerSound sound) ) return;
SpeakerSound sound = (SpeakerSound) event.getSound();
if( sound.stream == null ) return; if( sound.stream == null ) return;
event.getSource().attachBufferStream( sound.stream ); event.getSource().attachBufferStream( sound.stream );
event.getSource().play(); event.getSource().play();
sound.channel = event.getSource();
sound.executor = event.getManager().executor;
} }
public static SpeakerInstance getSound( UUID source ) public static SpeakerInstance getSound( UUID source )

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.AbstractSoundInstance; import net.minecraft.client.resources.sounds.AbstractSoundInstance;
import net.minecraft.client.resources.sounds.TickableSoundInstance; import net.minecraft.client.resources.sounds.TickableSoundInstance;
import net.minecraft.client.sounds.AudioStream; import net.minecraft.client.sounds.AudioStream;
@ -13,9 +14,12 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.concurrent.Executor;
public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance
{ {
Channel channel;
Executor executor;
DfpwmStream stream; DfpwmStream stream;
SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch ) SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch )

View File

@ -30,7 +30,36 @@ import java.util.OptionalLong;
import java.util.function.Function; 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 * @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. * @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. * @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.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 @LuaFunction
public final Object[] getDrive( String path ) throws LuaException public final Object[] getDrive( String path ) throws LuaException

View File

@ -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 * 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}. * 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. * polling.
* *
* This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of * This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of

View File

@ -19,10 +19,10 @@ import javax.annotation.Nonnull;
* *
* This works with energy storage blocks, as well as generators and machines which consume energy. * This works with energy storage blocks, as well as generators and machines which consume energy.
* *
* <blockquote> * :::note
* <strong>Note:</strong> Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF * Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF
* used/generated per tick). * used/generated per tick).
* </blockquote> * :::
* *
* @cc.module energy_storage * @cc.module energy_storage
*/ */

View File

@ -21,9 +21,60 @@ import java.util.HashSet;
import java.util.Set; 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.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 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} * Sends a modem message on a certain channel. Modems listening on the channel will queue a {@code modem_message}
* event on adjacent computers. * 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 channel The channel to send messages on.
* @param replyChannel The channel that responses to this message should be sent on. * @param replyChannel The channel that responses to this message should be sent on. This can be the same as
* @param payload The object to send. This can be a boolean, string, number, or table. * {@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. * @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 @LuaFunction
public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException

View File

@ -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 * If this computer is attached to the network, it _will not_ be included in
* this list. * this list.
* *
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @return Remote peripheral names on the network. * @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. * Determine if a peripheral is available on this wired network.
* *
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @param name The peripheral's name. * @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. * 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} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @param name The peripheral's name. * @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. * Check a peripheral is of a particular type.
* *
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @param name The peripheral's name. * @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. * 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} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @param name The peripheral's name. * @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. * Call a method on a peripheral on this wired network.
* *
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @param computer The calling computer. * @param computer The calling computer.
* @param context The Lua context we're executing in. * @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 * may be used by other computers on the network to wrap this computer as a
* peripheral. * peripheral.
* *
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} * :::note
* returns false before calling it.</blockquote> * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
* :::
* *
* @return The current computer's name. * @return The current computer's name.
* @cc.treturn string|nil The current computer's name on the wired network. * @cc.treturn string|nil The current computer's name on the wired network.

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaTable; import dan200.computercraft.api.lua.LuaTable;
import dan200.computercraft.shared.util.PauseAwareTimer;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import javax.annotation.Nonnull; 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 * 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. * 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; private static final int PREC = 10;
@ -36,7 +37,7 @@ class DfpwmState
private boolean previousBit = false; private boolean previousBit = false;
private boolean unplayed = true; private boolean unplayed = true;
private long clientEndTime = System.nanoTime(); private long clientEndTime = PauseAwareTimer.getTime();
private float pendingVolume = 1.0f; private float pendingVolume = 1.0f;
private ByteBuffer pendingAudio; private ByteBuffer pendingAudio;
@ -107,7 +108,7 @@ class DfpwmState
boolean isPlaying() boolean isPlaying()
{ {
return unplayed || clientEndTime >= System.nanoTime(); return unplayed || clientEndTime >= PauseAwareTimer.getTime();
} }
float getVolume() float getVolume()

View File

@ -17,6 +17,7 @@ import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage;
import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage; import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage;
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.util.PauseAwareTimer;
import net.minecraft.ResourceLocationException; import net.minecraft.ResourceLocationException;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket;
@ -119,7 +120,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
return; return;
} }
long now = System.nanoTime(); long now = PauseAwareTimer.getTime();
if( sound != null ) if( sound != null )
{ {
lastPlayTime = clock; lastPlayTime = clock;
@ -342,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
// TODO: Use ArgumentHelpers instead? // TODO: Use ArgumentHelpers instead?
int length = audio.length(); int length = audio.length();
if( length <= 0 ) throw new LuaException( "Cannot play empty audio" ); 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; DfpwmState state;
synchronized( lock ) synchronized( lock )
@ -387,17 +388,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
computers.remove( computer ); 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;
}
} }
} }

View File

@ -17,7 +17,6 @@ import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState; 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.computer.items.IComputerItem;
import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.pocket.apis.PocketAPI; 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.InteractionResult;
import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item; 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 ); PocketUpgrades.getVanillaUpgrades().map( x -> create( -1, null, -1, x ) ).forEach( stacks::add );
} }
private boolean tick( @Nonnull ItemStack stack, @Nonnull Level world, @Nonnull Entity entity, @Nonnull PocketServerComputer computer )
{
IPocketUpgrade upgrade = getUpgrade( stack );
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 );
}
// Sync label
String label = computer.getLabel();
if( !Objects.equal( label, getLabel( stack ) ) )
{
changed = true;
setLabel( stack, label );
}
// Update pocket upgrade
if( upgrade != null ) upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) );
return changed;
}
@Override @Override
public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected ) public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected )
{ {
if( !world.isClientSide ) if( !world.isClientSide )
{ {
// Server side Container inventory = entity instanceof Player player ? player.getInventory() : null;
Container inventory = entity instanceof Player ? ((Player) entity).getInventory() : null; PocketServerComputer computer = createServerComputer( world, entity, inventory, stack );
PocketServerComputer computer = createServerComputer( world, inventory, entity, stack ); computer.keepAlive();
if( computer != null )
{
IPocketUpgrade upgrade = getUpgrade( stack );
// Ping computer boolean changed = tick( stack, world, entity, computer );
computer.keepAlive(); if( changed && inventory != null ) inventory.setChanged();
computer.setLevel( world );
computer.updateValues( entity, stack, upgrade );
// Sync ID
int id = computer.getID();
if( id != getComputerID( stack ) )
{
setComputerID( stack, id );
if( inventory != null ) inventory.setChanged();
}
// Sync label
String label = computer.getLabel();
if( !Objects.equal( label, getLabel( stack ) ) )
{
setLabel( stack, label );
if( inventory != null ) inventory.setChanged();
}
// Update pocket upgrade
if( upgrade != null )
{
upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) );
}
}
} }
else else
{ {
// Client side
createClientComputer( stack ); 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 @Nonnull
@Override @Override
public InteractionResultHolder<ItemStack> use( Level world, Player player, @Nonnull InteractionHand hand ) 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 ); ItemStack stack = player.getItemInHand( hand );
if( !world.isClientSide ) if( !world.isClientSide )
{ {
PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, stack ); PocketServerComputer computer = createServerComputer( world, player, player.getInventory(), stack );
computer.turnOn();
boolean stop = false; boolean stop = false;
if( computer != null ) IPocketUpgrade upgrade = getUpgrade( stack );
if( upgrade != null )
{ {
computer.turnOn(); computer.updateValues( player, stack, upgrade );
stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) );
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; boolean isTypingOnly = hand == InteractionHand.OFF_HAND;
new ComputerContainerData( computer ).open( player, new PocketComputerMenuProvider( computer, stack, this, hand, isTypingOnly ) ); 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 ); 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; PocketServerComputer computer;
int instanceID = getInstanceID( stack ); int instanceID = getInstanceID( stack );
int sessionID = getSessionID( stack ); int sessionID = getSessionID( stack );
int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID(); int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID();
if( instanceID >= 0 && sessionID == correctSessionID && if( instanceID >= 0 && sessionID == correctSessionID && ComputerCraft.serverComputerRegistry.contains( instanceID ) )
ComputerCraft.serverComputerRegistry.contains( instanceID ) )
{ {
computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( 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" ); computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" );
setComputerID( stack, computerID ); setComputerID( stack, computerID );
} }
computer = new PocketServerComputer( computer = new PocketServerComputer( world, computerID, getLabel( stack ), instanceID, getFamily() );
world,
computerID,
getLabel( stack ),
instanceID,
getFamily()
);
computer.updateValues( entity, stack, getUpgrade( stack ) ); computer.updateValues( entity, stack, getUpgrade( stack ) );
computer.addAPI( new PocketAPI( computer ) ); computer.addAPI( new PocketAPI( computer ) );
ComputerCraft.serverComputerRegistry.add( instanceID, computer ); ComputerCraft.serverComputerRegistry.add( instanceID, computer );
@ -251,15 +253,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
return computer; return computer;
} }
public static ServerComputer getServerComputer( @Nonnull ItemStack stack ) @Nullable
public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack )
{ {
int session = getSessionID( stack ); int session = getSessionID( stack );
if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null; if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null;
int instanceID = getInstanceID( stack ); 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 ) public static ClientComputer createClientComputer( @Nonnull ItemStack stack )
{ {
int instanceID = getInstanceID( stack ); int instanceID = getInstanceID( stack );
@ -274,6 +278,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
return null; return null;
} }
@Nullable
private static ClientComputer getClientComputer( @Nonnull ItemStack stack ) private static ClientComputer getClientComputer( @Nonnull ItemStack stack )
{ {
int instanceID = getInstanceID( stack ); int instanceID = getInstanceID( stack );

View File

@ -22,8 +22,50 @@ import java.util.Map;
import java.util.Optional; 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.module turtle
* @cc.since 1.3 * @cc.since 1.3
*/ */

View File

@ -31,7 +31,6 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.FluidTags; import net.minecraft.tags.FluidTags;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.MoverType;
import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.Level; 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.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState; 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.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.IItemHandlerModifiable;
@ -48,6 +48,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR; import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR;
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
@ -65,6 +66,8 @@ public class TurtleBrain implements ITurtleAccess
private static final int ANIM_DURATION = 8; 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 TileTurtle owner;
private ComputerProxy proxy; private ComputerProxy proxy;
private GameProfile owningPlayer; private GameProfile owningPlayer;
@ -886,7 +889,7 @@ public class TurtleBrain implements ITurtleAccess
} }
AABB aabb = new AABB( minX, minY, minZ, maxX, maxY, maxZ ); 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() ) if( !list.isEmpty() )
{ {
double pushStep = 1.0f / ANIM_DURATION; double pushStep = 1.0f / ANIM_DURATION;

View File

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

View File

@ -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 public net.minecraft.client.gui.components.ChatComponent m_93803_(I)V # removeById
# NoTermComputerScreen # NoTermComputerScreen
public net.minecraft.client.Minecraft f_91080_ # screen 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

View File

@ -1,16 +1,18 @@
--- The Disk API allows you to interact with disk drives. --[[- The Disk API allows you to interact with disk drives.
--
-- These functions can operate on locally attached or remote disk drives. To use These functions can operate on locally attached or remote disk drives. To use a
-- a locally attached drive, specify “side” as one of the six sides locally attached drive, specify side as one of the six sides (e.g. `left`); to
-- (e.g. `left`); to use a remote disk drive, specify its name as printed when use a remote disk drive, specify its name as printed when enabling its modem
-- enabling its modem (e.g. `drive_0`). (e.g. `drive_0`).
--
-- **Note:** All computers (except command computers), turtles and pocket :::tip
-- computers can be placed within a disk drive to access it's internal storage All computers (except command computers), turtles and pocket computers can be
-- like a disk. placed within a disk drive to access it's internal storage like a disk.
-- :::
-- @module disk
-- @since 1.2 @module disk
@since 1.2
]]
local function isDrive(name) local function isDrive(name)
if type(name) ~= "string" then if type(name) ~= "string" then

View File

@ -1,27 +1,30 @@
--- The GPS API provides a method for turtles and computers to retrieve their --[[- The GPS API provides a method for turtles and computers to retrieve their
-- own locations. own locations.
--
-- It broadcasts a PING message over @{rednet} and wait for responses. In order It broadcasts a PING message over @{rednet} and wait for responses. In order for
-- for this system to work, there must be at least 4 computers used as gps hosts this system to work, there must be at least 4 computers used as gps hosts which
-- which will respond and allow trilateration. Three of these hosts should be in will respond and allow trilateration. Three of these hosts should be in a plane,
-- a plane, and the fourth should be either above or below the other three. The and the fourth should be either above or below the other three. The three in a
-- three in a plane should not be in a line with each other. You can set up plane should not be in a line with each other. You can set up hosts using the
-- hosts using the gps program. gps program.
--
-- **Note**: When entering in the coordinates for the host you need to put in :::note
-- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all When entering in the coordinates for the host you need to put in the `x`, `y`,
-- rednet distances are measured from the block the computer is in. 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 Also note that you may choose which axes x, y, or z refers to - so long as your
-- [this tutorial][1], using z to account for height, or you might use y to systems have the same definition as any GPS servers that're in range, it works
-- account for height in the way that Minecraft's debug screen displays. 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
-- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/ height in the way that Minecraft's debug screen displays.
--
-- @module gps [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/
-- @since 1.31
@module gps
@since 1.31
]]
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect

View File

@ -2,13 +2,13 @@
Functions are not actually executed simultaniously, but rather this API will Functions are not actually executed simultaniously, but rather this API will
automatically switch between them whenever they yield (eg whenever they call 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 functions that call that, etc - basically, anything that causes the function
to "pause"). to "pause").
Each function executed in "parallel" gets its own copy of the event queue, Each function executed in "parallel" gets its own copy of the event queue,
and so "event consuming" functions (again, mostly anything that causes the 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 etc) can safely be used in one without affecting the event queue accessed by
the other. the other.

View File

@ -66,7 +66,7 @@ type.
What is a peripheral type though? This is a string which describes what a 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 peripheral is, and so what functions are available on it. For instance, speakers
are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might 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"`. `"inventory"`.
You can get all the types a peripheral has with @{peripheral.getType}, and check You can get all the types a peripheral has with @{peripheral.getType}, and check

View File

@ -1,21 +1,48 @@
--- The Rednet API allows systems to communicate between each other without --[[- The Rednet API allows computers to communicate between each other by using
-- using redstone. It serves as a wrapper for the modem API, offering ease of @{modem|modems}. It provides a layer of abstraction on top of the main @{modem}
-- functionality (particularly in regards to repeating signals) with some peripheral, making it slightly easier to use.
-- expense of fine control.
-- ## Basic usage
-- In order to send and receive data, a modem (either wired, wireless, or ender) In order to send a message between two computers, each computer must have a
-- is required. The data reaches any possible destinations immediately after modem on one of its sides (or in the case of pocket computers and turtles, the
-- sending it, but is range limited. 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.
-- Rednet also allows you to use a "protocol" - simple string names indicating
-- what messages are about. Receiving systems may filter messages according to Once rednet is opened, you can send messages using @{rednet.send} and receive
-- their protocols, thereby automatically ignoring incoming messages which don't them using @{rednet.receive}. It's also possible to send a message to _every_
-- specify an identical string. It's also possible to @{rednet.lookup|lookup} rednet-using computer using @{rednet.broadcast}.
-- which systems in the area use certain protocols, hence making it easier to
-- determine where given messages should be sent in the first place. :::caution Network security
--
-- @module rednet While rednet provides a friendly way to send messages to specific computers, it
-- @since 1.2 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 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. @tparam string modem The name of the modem to open.
@throws If there is no such modem with the given name @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") 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) function open(modem)
expect(1, modem, "string") expect(1, modem, "string")
@ -65,6 +100,7 @@ end
-- @tparam[opt] string modem The side the modem exists on. If not given, all -- @tparam[opt] string modem The side the modem exists on. If not given, all
-- open modems will be closed. -- open modems will be closed.
-- @throws If there is no such modem with the given name -- @throws If there is no such modem with the given name
-- @see rednet.open
function close(modem) function close(modem)
expect(1, modem, "string", "nil") expect(1, modem, "string", "nil")
if modem then if modem then
@ -90,6 +126,7 @@ end
-- modems will be checked. -- modems will be checked.
-- @treturn boolean If the given modem is open. -- @treturn boolean If the given modem is open.
-- @since 1.31 -- @since 1.31
-- @see rednet.open
function isOpen(modem) function isOpen(modem)
expect(1, modem, "string", "nil") expect(1, modem, "string", "nil")
if modem then if modem then
@ -109,15 +146,17 @@ function isOpen(modem)
end end
--[[- Allows a computer or turtle with an attached modem to send a message --[[- 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. be @{rednet.open|opened} before sending is possible.
Assuming the target was in range and also had a correctly opened modem, it Assuming the target was in range and also had a correctly opened modem, the
may then use @{rednet.receive} to collect the message. target computer may then use @{rednet.receive} to collect the message.
@tparam number recipient The ID of the receiving computer. @tparam number recipient The ID of the receiving computer.
@param message The message to send. This should not contain coroutines or @param message The message to send. Like with @{modem.transmit}, this can
functions, as they will be converted to @{nil}. 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 @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 using @{rednet.receive} one can filter to only receive messages sent under a
particular protocol. particular protocol.
@ -174,16 +213,19 @@ function send(recipient, message, protocol)
return sent return sent
end end
--- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} --[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
-- channel. The message will be received by every device listening to rednet. channel. The message will be received by every device listening to rednet.
--
-- @param message The message to send. This should not contain coroutines or @param message The message to send. This should not contain coroutines or
-- functions, as they will be converted to @{nil}. functions, as they will be converted to @{nil}. @tparam[opt] string protocol
-- @tparam[opt] string protocol The "protocol" to send this message under. When The "protocol" to send this message under. When using @{rednet.receive} one can
-- using @{rednet.receive} one can filter to only receive messages sent under a filter to only receive messages sent under a particular protocol.
-- particular protocol. @see rednet.receive
-- @see rednet.receive @changed 1.6 Added protocol parameter.
-- @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) function broadcast(message, protocol)
expect(2, protocol, "string", "nil") expect(2, protocol, "string", "nil")
send(CHANNEL_BROADCAST, message, protocol) send(CHANNEL_BROADCAST, message, protocol)
@ -263,25 +305,25 @@ function receive(protocol_filter, timeout)
end end
end end
--- Register the system as "hosting" the desired protocol under the specified --[[- Register the system as "hosting" the desired protocol under the specified
-- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
-- maybe name) on the same network, the registered system will automatically maybe name) on the same network, the registered system will automatically
-- respond via a background process, hence providing the system performing the respond via a background process, hence providing the system performing the
-- lookup with its ID number. lookup with its ID number.
--
-- Multiple computers may not register themselves on the same network as having Multiple computers may not register themselves on the same network as having the
-- the same names against the same protocols, and the title `localhost` is same names against the same protocols, and the title `localhost` is specifically
-- specifically reserved. They may, however, share names as long as their hosted reserved. They may, however, share names as long as their hosted protocols are
-- protocols are different, or if they only join a given network after different, or if they only join a given network after "registering" themselves
-- "registering" themselves before doing so (eg while offline or part of a before doing so (eg while offline or part of a different network).
-- different network).
-- @tparam string protocol The protocol this computer provides.
-- @tparam string protocol The protocol this computer provides. @tparam string hostname The name this protocol exposes for the given protocol.
-- @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.
-- @throws If trying to register a hostname which is reserved, or currently in use. @see rednet.unhost
-- @see rednet.unhost @see rednet.lookup
-- @see rednet.lookup @since 1.6
-- @since 1.6 ]]
function host(protocol, hostname) function host(protocol, hostname)
expect(1, protocol, "string") expect(1, protocol, "string")
expect(2, hostname, "string") expect(2, hostname, "string")
@ -306,21 +348,38 @@ function unhost(protocol)
hostnames[protocol] = nil hostnames[protocol] = nil
end end
--- Search the local rednet network for systems @{rednet.host|hosting} the --[[- Search the local rednet network for systems @{rednet.host|hosting} the
-- desired protocol and returns any computer IDs that respond as "registered" desired protocol and returns any computer IDs that respond as "registered"
-- against it. against it.
--
-- If a hostname is specified, only one ID will be returned (assuming an exact If a hostname is specified, only one ID will be returned (assuming an exact
-- match is found). match is found).
--
-- @tparam string protocol The protocol to search for. @tparam string protocol The protocol to search for.
-- @tparam[opt] string hostname The hostname to search for. @tparam[opt] string hostname The hostname to search for.
--
-- @treturn[1] { number }|nil A list of computer IDs hosting the given @treturn[1] number... A list of computer IDs hosting the given protocol.
-- protocol, or @{nil} if none exist. @treturn[2] number|nil The computer ID with the provided hostname and protocol,
-- @treturn[2] number|nil The computer ID with the provided hostname and protocol, or @{nil} if none exists.
-- or @{nil} if none exists. @since 1.6
-- @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) function lookup(protocol, hostname)
expect(1, protocol, "string") expect(1, protocol, "string")
expect(2, hostname, "string", "nil") expect(2, hostname, "string", "nil")

View File

@ -108,39 +108,48 @@ local function makePagedScroll(_term, _nFreeLines)
end end
end end
--- Prints a given string to the display. --[[- Prints a given string to the display.
--
-- If the action can be completed without scrolling, it acts much the same as 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 @{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 the bottom of the display. Each press will cause it to scroll down and write a
-- a single line more before prompting again, if need be. single line more before prompting again, if need be.
--
-- @tparam string _sText The text to print to the screen. @tparam string text The text to print to the screen.
-- @tparam[opt] number _nFreeLines The number of lines which will be @tparam[opt] number free_lines The number of lines which will be
-- automatically scrolled before the first prompt appears (meaning _nFreeLines + automatically scrolled before the first prompt appears (meaning free_lines +
-- 1 lines will be printed). This can be set to the terminal's height - 2 to 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 always try to fill the screen. Defaults to 0, meaning only one line is
-- displayed before prompting. displayed before prompting.
-- @treturn number The number of lines printed. @treturn number The number of lines printed.
-- @usage
-- local width, height = term.getSize() @usage Generates several lines of text and then prints it, paging once the
-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2) bottom of the terminal is reached.
function pagedPrint(_sText, _nFreeLines)
expect(2, _nFreeLines, "number", "nil") 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 -- Setup a redirector
local oldTerm = term.current() local oldTerm = term.current()
local newTerm = {} local newTerm = {}
for k, v in pairs(oldTerm) do for k, v in pairs(oldTerm) do
newTerm[k] = v newTerm[k] = v
end end
newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines)
newTerm.scroll = makePagedScroll(oldTerm, free_lines)
term.redirect(newTerm) term.redirect(newTerm)
-- Print the text -- Print the text
local result local result
local ok, err = pcall(function() local ok, err = pcall(function()
if _sText ~= nil then if text ~= nil then
result = print(_sText) result = print(text)
else else
result = print() result = print()
end end
@ -214,32 +223,45 @@ local function tabulateCommon(bPaged, ...)
end end
end end
--- Prints tables in a structured form. --[[- Prints tables in a structured form.
--
-- This accepts multiple arguments, either a table or a number. When 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 encountering a table, this will be treated as a table row, with each column
-- width being auto-adjusted. width being auto-adjusted.
--
-- When encountering a number, this sets the text color of the subsequent rows to it. 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. @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
-- @since 1.3 @usage
textutils.tabulate(
colors.orange, { "1", "2", "3" },
colors.lightBlue, { "A", "B", "C" }
)
]]
function tabulate(...) function tabulate(...)
return tabulateCommon(false, ...) return tabulateCommon(false, ...)
end end
--- Prints tables in a structured form, stopping and prompting for input should --[[- Prints tables in a structured form, stopping and prompting for input should
-- the result not fit on the terminal. the result not fit on the terminal.
--
-- This functions identically to @{textutils.tabulate}, but will prompt for user This functions identically to @{textutils.tabulate}, but will prompt for user
-- input should the whole output not fit on the display. input should the whole output not fit on the display.
--
-- @tparam {string...}|number ... The rows and text colors to 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.tabulate @see textutils.pagedPrint
-- @see textutils.pagedPrint @since 1.3
-- @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(...) function pagedTabulate(...)
return tabulateCommon(true, ...) return tabulateCommon(true, ...)
end end
@ -692,11 +714,11 @@ saving in a file or pretty-printing.
@throws If the object contains a value which cannot be @throws If the object contains a value which cannot be
serialised. This includes functions and tables which appear multiple serialised. This includes functions and tables which appear multiple
times. times.
@see cc.pretty.pretty An alternative way to display a table, often more suitable for @see cc.pretty.pretty_print An alternative way to display a table, often more
pretty printing. suitable for pretty printing.
@since 1.3 @since 1.3
@changed 1.97.0 Added `opts` argument. @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 } }) textutils.serialise({ 1, 2, 3, a = 1, ["another key"] = { true } })

View File

@ -1,6 +1,4 @@
--- The turtle API allows you to control your turtle. --- @module turtle
--
-- @module turtle
if not turtle then if not turtle then
error("Cannot load turtle API on computer", 2) error("Cannot load turtle API on computer", 2)
@ -8,9 +6,8 @@ end
--- The builtin turtle API, without any generated helper functions. --- The builtin turtle API, without any generated helper functions.
-- --
-- Generally you should not need to use this table - it only exists for -- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the base. You
-- backwards compatibility reasons. -- should not need to use it.
-- @deprecated
native = turtle.native or turtle native = turtle.native or turtle
local function addCraftMethod(object) local function addCraftMethod(object)

View File

@ -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 # New features in CC: Tweaked 1.99.1
* Add package.searchpath to the cc.require API. (MCJack123) * Add package.searchpath to the cc.require API. (MCJack123)

View File

@ -6,7 +6,7 @@ Uses LuaJ from http://luaj.sourceforge.net/
The ComputerCraft 1.76 update was sponsored by MinecraftU and Deep Space. 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. 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! Follow @DanTwoHundred on Twitter!
To help contribute to ComputerCraft, browse the source code at https://github.com/dan200/ComputerCraft. To help contribute to ComputerCraft, browse the source code at https://github.com/dan200/ComputerCraft.

View File

@ -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) * Speakers can now play arbitrary PCM audio.
* Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases. * 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: Several bug fixes:
* Fix keys being "sticky" when opening the off-hand pocket computer GUI. * Fix the "repeat" program not repeating broadcast rednet messages.
* Correctly handle broken coroutine managers resuming Java code with a `nil` event. * Fix the drag-and-drop upload functionality writing empty files.
* Prevent computer buttons stealing focus from the terminal. * Prevent turtles from pushing non-pushable entities.
* Fix a class cast exception when a monitor is malformed in ways I do not quite understand.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@ -36,7 +36,7 @@ local encoder = dfpwm.make_encoder()
local decoder = dfpwm.make_decoder() local decoder = dfpwm.make_decoder()
local out = fs.open("speedy.dfpwm", "wb") 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 decoded = decoder(input)
local output = {} local output = {}

View File

@ -224,8 +224,8 @@ end
--- Display a document on the terminal. --- Display a document on the terminal.
-- --
-- @tparam Doc doc The document to render -- @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) local function write(doc, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
expect(2, ribbon_frac, "number", "nil") expect(2, ribbon_frac, "number", "nil")
@ -286,8 +286,8 @@ end
--- Display a document on the terminal with a trailing new line. --- Display a document on the terminal with a trailing new line.
-- --
-- @tparam Doc doc The document to render. -- @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) local function print(doc, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
expect(2, ribbon_frac, "number", "nil") expect(2, ribbon_frac, "number", "nil")
@ -297,10 +297,10 @@ end
--- Render a document, converting it into a string. --- Render a document, converting it into a string.
-- --
-- @tparam Doc doc The document to render. -- @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 -- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to fit
-- fit this width - it is only used for finding the best layout. -- 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=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
-- @treturn string The rendered document as a string. -- @treturn string The rendered document as a string.
local function render(doc, width, ribbon_frac) local function render(doc, width, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end 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_args`: Show the arguments to a function if known (`false` by default).
- `function_source`: Show where the function was defined, instead of - `function_source`: Show where the function was defined, instead of
`function: xxxxxxxx` (`false` by default). `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. @usage Display a table on the screen.

View File

@ -1,22 +1,24 @@
--- This provides a pure Lua implementation of the builtin @{require} function --[[- This provides a pure Lua implementation of the builtin @{require} function
-- and @{package} library. and @{package} library.
--
-- Generally you do not need to use this module - it is injected into the Generally you do not need to use this module - it is injected into the every
-- every program's environment. However, it may be useful when building a program's environment. However, it may be useful when building a custom shell or
-- custom shell or when running programs yourself. when running programs yourself.
--
-- @module cc.require @module cc.require
-- @since 1.88.0 @since 1.88.0
-- @usage Construct the package and require function, and insert them into a @see using_require For an introduction on how to use @{require}.
-- custom environment. @usage Construct the package and require function, and insert them into a
-- custom environment.
-- local r = require "cc.require"
-- local env = setmetatable({}, { __index = _ENV }) local r = require "cc.require"
-- env.require, env.package = r.make(env, "/") 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" -- Now we have our own require function, separate to the original.
-- print(r, r2) 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 = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
local expect = expect.expect local expect = expect.expect

View File

@ -171,7 +171,7 @@ end
local current_section = nil local current_section = nil
local offset = 0 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. -- This could potentially be a binary search, but right now it's not worth it.
local function find_section() local function find_section()

View File

@ -26,13 +26,14 @@ const Click = (options: { run: () => void }) =>
type WindowProps = {}; type WindowProps = {};
type WindowState = { type Example = {
visible: boolean, files: { [file: string]: string },
example: string,
exampleIdx: number,
} }
type WindowState = {
exampleIdx: number,
} & ({ visible: false, example: null } | { visible: true, example: Example })
type Touch = { clientX: number, clientY: number }; type Touch = { clientX: number, clientY: number };
class Window extends Component<WindowProps, WindowState> { class Window extends Component<WindowProps, WindowState> {
@ -41,12 +42,14 @@ class Window extends Component<WindowProps, WindowState> {
private top: number = 0; private top: number = 0;
private dragging?: { downX: number, downY: number, initialX: number, initialY: number }; private dragging?: { downX: number, downY: number, initialX: number, initialY: number };
private snippets: { [file: string]: string } = {};
constructor(props: WindowProps, context: unknown) { constructor(props: WindowProps, context: unknown) {
super(props, context); super(props, context);
this.state = { this.state = {
visible: false, visible: false,
example: "", example: null,
exampleIdx: 0, exampleIdx: 0,
} }
} }
@ -57,10 +60,16 @@ class Window extends Component<WindowProps, WindowState> {
const element = elements[i] as HTMLElement; const element = elements[i] as HTMLElement;
let example = element.innerText; let example = element.innerText;
const snippet = element.getAttribute("data-snippet");
if (snippet) this.snippets[snippet] = example;
if (element.getAttribute("data-lua-kind") == "expr") { if (element.getAttribute("data-lua-kind") == "expr") {
example = exprTemplate.replace("__expr__", example); 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>
<div class="computer-container"> <div class="computer-container">
<Computer key={exampleIdx} files={{ <Computer key={exampleIdx} files={{
"example.lua": example, ...defaultFiles ...example!.files, ...defaultFiles
}} /> }} />
</div> </div>
</div> : <div class="example-window example-window-hidden" />; </div> : <div class="example-window example-window-hidden" />;
} }
private runExample(example: string): () => void { private runExample(example: string, mount: string | null): () => void {
return () => { return () => {
if (!this.positioned) { if (!this.positioned) {
this.positioned = true; this.positioned = true;
@ -90,9 +99,17 @@ class Window extends Component<WindowProps, WindowState> {
this.top = 20; 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) => ({ this.setState(({ exampleIdx }: WindowState) => ({
visible: true, visible: true,
example: example, example: { files },
exampleIdx: exampleIdx + 1, exampleIdx: exampleIdx + 1,
})); }));
} }