Compare commits
19 Commits
44ed7456a8
...
21961d0049
Author | SHA1 | Date |
---|---|---|
Lupus590 | 21961d0049 | |
Jonathan Coates | 00e2e2bd2d | |
Jonathan Coates | 7c1f40031b | |
Lupus590 | 8d3e1c5be9 | |
Lupus590 | 1d5463d2dd | |
Lupus590 | e43314dd35 | |
Lupus590 | 67706cfb83 | |
Lupus590 | f835bb7f11 | |
Lupus590 | f583df3392 | |
Lupus590 | 19e2afc6a6 | |
Lupus590 | 8eb463b6ae | |
Lupus590 | 48fcd701ac | |
Lupus590 | 71409fac43 | |
Lupus590 | 3823202b63 | |
Lupus590 | ae48f5dda9 | |
Lupus590 | cd6fd8e6ca | |
Lupus590 | f3935280d6 | |
Lupus590 | 89899f0ee2 | |
Lupus590 | 190c7c0221 |
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
module: [kind=guide] startup_files
|
||||
---
|
||||
|
||||
# Running code when a computer turns on
|
||||
|
||||
:::note Use case
|
||||
You might be aware that CC computers restart when the chunk they are in reloads, startup files provides a way to have the CC computer run arbitrary code after it has finished turning on.
|
||||
:::
|
||||
|
||||
:::note Mass configuration
|
||||
Unless disabled in the server config, CC computers will look for startup files on disks in attached disk drives. This means that a freshly crafted computer can automatically load a program from that disk startup file.
|
||||
|
||||
Apply this to turtles with a nearby chest containing fuel and you can have a turtle factory that automatically programs the turtles after creating them.
|
||||
:::
|
||||
|
||||
CC computers will look for a file or folder with a special name when it finishes loading. Also, CC has a fixed order in which it looks for these files and uses them.
|
||||
|
||||
## The magic names
|
||||
All startup *files* have to be at the root of their drive, this drive is commonly the computer's internal storage, but can be a disk too.
|
||||
```
|
||||
startup
|
||||
startup.lua
|
||||
```
|
||||
`startup` may be a file or a folder, the behavior of it changes depending on which it is. If it's a file then it is ran, if it's a folder then each file inside it is ran.
|
||||
|
||||
`startup.lua` has to be a file.
|
||||
|
||||
Note that these are all lower case, case matters sometimes so keep to the correct one - more info in a note on the @{fs} API page.
|
||||
|
||||
|
||||
## Searching drives for startup files
|
||||
If `shell.allow_disk_startup` is true (which it is by default), then the CC computer will look for disk drives that have at least one startup file, it only uses the first disk that fulfils these criteria. Disk drives are searched in the order they are found via `peripheral.getNames()`. It then runs the found startup files as described below.
|
||||
|
||||
If `shell.allow_startup` is true (which it is by default) and no disk startup file was used, then the computer will look for startup files in its internal storage. Valid files are run as described below.
|
||||
|
||||
### Running the startup files
|
||||
If `startup` is a file then this file and *only* this file is run, even if a `startup.lua` exists on the same device.
|
||||
|
||||
If `startup` is not a file (either it's a folder or it doesn't exist) then `startup.lua` is run, assuming it exists. After running `startup.lua` (or not if it didn't exist), the CC computer then runs each file in the `startup` folder one after the other in the order that @{fs.list} returns them.
|
||||
|
||||
:::note Example
|
||||
Let's say that I have a computer with a startup file on its internal storage, and is connected to six disk drives. Two of these drives have disks that have startup files, one has a disk with no files, one with files but none of them are startup files, one has a music disk (the kind that you can place in a jukebox), the remaining drive has no disk in it.
|
||||
|
||||
Both of the settings that affect startup are set to allow their respective startup modes.
|
||||
|
||||
I turn on my computer, it looks for disk drives. It so happens that the first one it finds is the empty one, since this drive has no disk it goes to the next drive it found. The second drive that it searches for startup files is the jukebox music disk, this one is also skipped as music disks don't have startup files. Third, it finds the disk drive containing the disk without files, it goes to the next drive. Forth is the disk with files but without startup files, it tries the next disk.
|
||||
|
||||
We are down to the two disks both with startup files, the computer finds one of them and searches it for startup files, it finds the files and runs them in the order above. When/if these startup files exit without shutting down the computer then the CC computer drops into the interactive prompt. The computer *didn't* look at the last disk drive and *didn't* use the startup files on its internal storage.
|
||||
:::
|
||||
|
||||
:::note Computers don't remember what they were doing when the chunk unloads
|
||||
You may have heard of the term "persistence", Computercraft computers do not have persistence. Startup files are currently the closest that CC has to persistence.
|
||||
|
||||
If you don't know what persistence is, if CC computers were persistent then they would remember what they were doing when the chunk they are in was unloaded, and resume running the task they were doing when the chunk gets reloaded.
|
||||
|
||||
However, Computercraft doesn't have persistence and thus restart as if they were just turned on. Mentioning turning on, they will remember that they were running and when the chunk reloads they will turn themselves on and start looking for startup files. Clever use of startup files and recording data to disk can somewhat circumvent the lack of built in persistence.
|
||||
|
||||
For most programs (usually running on computers), restarting from the beginning of the program is not an issue. However, turtles commonly have startup complications due to their ability to move. Unless you make sure that the turtle is in a fixed starting position every time (which is not always practical), your turtle programs will need some way to cope with the turtle starting in a different position - perhaps with the @{gps} API.
|
||||
|
||||
Incase it's not clear, Minecraft unloads all chunks (including force loaded chunks with chunk loaders) when the server restarts and when the single player world closes. So you may want startup files even if you are chunk loading your computers.
|
||||
:::
|
|
@ -49,31 +49,31 @@ public void queueEvent(String event, @Nullable Object[] arguments) {
|
|||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
|
||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyUp(int key) {
|
||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
|
||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClick(int button, int x, int y) {
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseUp(int button, int x, int y) {
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.UP, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDrag(int button, int x, int y) {
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.DRAG, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(int direction, int x, int y) {
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
|
||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.SCROLL, direction, x, y));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public void handleComputerTerminal(int containerId, TerminalState terminal) {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void handleMonitorData(BlockPos pos, TerminalState terminal) {
|
||||
public void handleMonitorData(BlockPos pos, @Nullable TerminalState terminal) {
|
||||
var player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
|
||||
|
@ -67,7 +67,7 @@ public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable
|
|||
}
|
||||
|
||||
@Override
|
||||
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal) {
|
||||
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, @Nullable TerminalState terminal) {
|
||||
ClientPocketComputers.setState(instanceId, state, lightState, terminal);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public static void remove(UUID id) {
|
|||
* @param lightColour The current colour of the modem light.
|
||||
* @param terminalData The current terminal contents.
|
||||
*/
|
||||
public static void setState(UUID instanceId, ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
public static void setState(UUID instanceId, ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
|
||||
var computer = instances.get(instanceId);
|
||||
if (computer == null) {
|
||||
instances.put(instanceId, new PocketComputerData(state, lightColour, terminalData));
|
||||
|
|
|
@ -26,10 +26,10 @@ public final class PocketComputerData {
|
|||
private ComputerState state;
|
||||
private int lightColour;
|
||||
|
||||
PocketComputerData(ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
PocketComputerData(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
|
||||
this.state = state;
|
||||
this.lightColour = lightColour;
|
||||
if (terminalData.hasTerminal()) terminal = terminalData.create();
|
||||
if (terminalData != null) terminal = terminalData.create();
|
||||
}
|
||||
|
||||
public int getLightState() {
|
||||
|
@ -44,11 +44,11 @@ public ComputerState getState() {
|
|||
return state;
|
||||
}
|
||||
|
||||
void setState(ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
void setState(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
|
||||
this.state = state;
|
||||
this.lightColour = lightColour;
|
||||
|
||||
if (terminalData.hasTerminal()) {
|
||||
if (terminalData != null) {
|
||||
if (terminal == null) {
|
||||
terminal = terminalData.create();
|
||||
} else {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
@ -29,6 +29,6 @@ protected final Component getDefaultName() {
|
|||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return BlockEntityHelpers.isUsable(this, player, BlockEntityHelpers.DEFAULT_INTERACT_RANGE);
|
||||
return Container.stillValidBlockEntity(this, player);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.LockCode;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.Nameable;
|
||||
|
@ -75,13 +76,13 @@ public void setRemoved() {
|
|||
unload();
|
||||
}
|
||||
|
||||
protected double getInteractRange() {
|
||||
return BlockEntityHelpers.DEFAULT_INTERACT_RANGE;
|
||||
protected int getInteractRange() {
|
||||
return Container.DEFAULT_DISTANCE_LIMIT;
|
||||
}
|
||||
|
||||
public boolean isUsable(Player player) {
|
||||
return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
|
||||
&& BlockEntityHelpers.isUsable(this, player, getInteractRange());
|
||||
&& Container.stillValidBlockEntity(this, player, getInteractRange());
|
||||
}
|
||||
|
||||
protected void serverTick() {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -21,68 +22,49 @@ public class TerminalState {
|
|||
private final boolean colour;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
@Nullable
|
||||
private final ByteBuf buffer;
|
||||
|
||||
public TerminalState(@Nullable NetworkedTerminal terminal) {
|
||||
if (terminal == null) {
|
||||
colour = false;
|
||||
width = height = 0;
|
||||
buffer = null;
|
||||
} else {
|
||||
colour = terminal.isColour();
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
public TerminalState(NetworkedTerminal terminal) {
|
||||
colour = terminal.isColour();
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
|
||||
var buf = buffer = Unpooled.buffer();
|
||||
terminal.write(new FriendlyByteBuf(buf));
|
||||
}
|
||||
var buf = buffer = Unpooled.buffer();
|
||||
terminal.write(new FriendlyByteBuf(buf));
|
||||
}
|
||||
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable TerminalState create(@Nullable NetworkedTerminal terminal) {
|
||||
return terminal == null ? null : new TerminalState(terminal);
|
||||
}
|
||||
|
||||
public TerminalState(FriendlyByteBuf buf) {
|
||||
colour = buf.readBoolean();
|
||||
width = buf.readVarInt();
|
||||
height = buf.readVarInt();
|
||||
|
||||
if (buf.readBoolean()) {
|
||||
width = buf.readVarInt();
|
||||
height = buf.readVarInt();
|
||||
|
||||
var length = buf.readVarInt();
|
||||
buffer = buf.readBytes(length);
|
||||
} else {
|
||||
width = height = 0;
|
||||
buffer = null;
|
||||
}
|
||||
var length = buf.readVarInt();
|
||||
buffer = buf.readBytes(length);
|
||||
}
|
||||
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeBoolean(colour);
|
||||
|
||||
buf.writeBoolean(buffer != null);
|
||||
if (buffer != null) {
|
||||
buf.writeVarInt(width);
|
||||
buf.writeVarInt(height);
|
||||
buf.writeVarInt(buffer.readableBytes());
|
||||
buf.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTerminal() {
|
||||
return buffer != null;
|
||||
buf.writeVarInt(width);
|
||||
buf.writeVarInt(height);
|
||||
buf.writeVarInt(buffer.readableBytes());
|
||||
buf.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return buffer == null ? 0 : buffer.readableBytes();
|
||||
return buffer.readableBytes();
|
||||
}
|
||||
|
||||
public void apply(NetworkedTerminal terminal) {
|
||||
if (buffer == null) throw new NullPointerException("buffer");
|
||||
terminal.resize(width, height);
|
||||
terminal.read(new FriendlyByteBuf(buffer));
|
||||
}
|
||||
|
||||
public NetworkedTerminal create() {
|
||||
if (buffer == null) throw new NullPointerException("Terminal does not exist");
|
||||
var terminal = new NetworkedTerminal(width, height, colour);
|
||||
terminal.read(new FriendlyByteBuf(buffer));
|
||||
return terminal;
|
||||
|
|
|
@ -26,11 +26,11 @@ public interface ClientNetworkContext {
|
|||
|
||||
void handleComputerTerminal(int containerId, TerminalState terminal);
|
||||
|
||||
void handleMonitorData(BlockPos pos, TerminalState terminal);
|
||||
void handleMonitorData(BlockPos pos, @Nullable TerminalState terminal);
|
||||
|
||||
void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name);
|
||||
|
||||
void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal);
|
||||
void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, @Nullable TerminalState terminal);
|
||||
|
||||
void handlePocketComputerDeleted(UUID instanceId);
|
||||
|
||||
|
|
|
@ -11,25 +11,26 @@
|
|||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> {
|
||||
private final BlockPos pos;
|
||||
private final TerminalState state;
|
||||
private final @Nullable TerminalState state;
|
||||
|
||||
public MonitorClientMessage(BlockPos pos, TerminalState state) {
|
||||
public MonitorClientMessage(BlockPos pos, @Nullable TerminalState state) {
|
||||
this.pos = pos;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public MonitorClientMessage(FriendlyByteBuf buf) {
|
||||
pos = buf.readBlockPos();
|
||||
state = new TerminalState(buf);
|
||||
state = buf.readNullable(TerminalState::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeBlockPos(pos);
|
||||
state.write(buf);
|
||||
buf.writeNullable(state, (b, t) -> t.write(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
|
@ -13,6 +12,7 @@
|
|||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
@ -22,20 +22,20 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
|
|||
private final UUID clientId;
|
||||
private final ComputerState state;
|
||||
private final int lightState;
|
||||
private final TerminalState terminal;
|
||||
private final @Nullable TerminalState terminal;
|
||||
|
||||
public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) {
|
||||
clientId = computer.getInstanceUUID();
|
||||
state = computer.getState();
|
||||
lightState = computer.getLight();
|
||||
terminal = sendTerminal ? computer.getTerminalState() : new TerminalState((NetworkedTerminal) null);
|
||||
terminal = sendTerminal ? computer.getTerminalState() : null;
|
||||
}
|
||||
|
||||
public PocketComputerDataMessage(FriendlyByteBuf buf) {
|
||||
clientId = buf.readUUID();
|
||||
state = buf.readEnum(ComputerState.class);
|
||||
lightState = buf.readVarInt();
|
||||
terminal = new TerminalState(buf);
|
||||
terminal = buf.readNullable(TerminalState::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,7 +43,7 @@ public void write(FriendlyByteBuf buf) {
|
|||
buf.writeUUID(clientId);
|
||||
buf.writeEnum(state);
|
||||
buf.writeVarInt(lightState);
|
||||
terminal.write(buf);
|
||||
buf.writeNullable(terminal, (b, t) -> t.write(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,14 +12,10 @@
|
|||
|
||||
|
||||
public class KeyEventServerMessage extends ComputerServerMessage {
|
||||
public static final int TYPE_DOWN = 0;
|
||||
public static final int TYPE_REPEAT = 1;
|
||||
public static final int TYPE_UP = 2;
|
||||
|
||||
private final int type;
|
||||
private final Action type;
|
||||
private final int key;
|
||||
|
||||
public KeyEventServerMessage(AbstractContainerMenu menu, int type, int key) {
|
||||
public KeyEventServerMessage(AbstractContainerMenu menu, Action type, int key) {
|
||||
super(menu);
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
|
@ -27,24 +23,24 @@ public KeyEventServerMessage(AbstractContainerMenu menu, int type, int key) {
|
|||
|
||||
public KeyEventServerMessage(FriendlyByteBuf buf) {
|
||||
super(buf);
|
||||
type = buf.readByte();
|
||||
type = buf.readEnum(Action.class);
|
||||
key = buf.readVarInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeByte(type);
|
||||
buf.writeEnum(type);
|
||||
buf.writeVarInt(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
var input = container.getInput();
|
||||
if (type == TYPE_UP) {
|
||||
if (type == Action.UP) {
|
||||
input.keyUp(key);
|
||||
} else {
|
||||
input.keyDown(key, type == TYPE_REPEAT);
|
||||
input.keyDown(key, type == Action.REPEAT);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,4 +48,8 @@ protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
|||
public MessageType<KeyEventServerMessage> type() {
|
||||
return NetworkMessages.KEY_EVENT;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
DOWN, REPEAT, UP
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,12 @@
|
|||
|
||||
|
||||
public class MouseEventServerMessage extends ComputerServerMessage {
|
||||
public static final int TYPE_CLICK = 0;
|
||||
public static final int TYPE_DRAG = 1;
|
||||
public static final int TYPE_UP = 2;
|
||||
public static final int TYPE_SCROLL = 3;
|
||||
|
||||
private final int type;
|
||||
private final Action type;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int arg;
|
||||
|
||||
public MouseEventServerMessage(AbstractContainerMenu menu, int type, int arg, int x, int y) {
|
||||
public MouseEventServerMessage(AbstractContainerMenu menu, Action type, int arg, int x, int y) {
|
||||
super(menu);
|
||||
this.type = type;
|
||||
this.arg = arg;
|
||||
|
@ -32,7 +27,7 @@ public MouseEventServerMessage(AbstractContainerMenu menu, int type, int arg, in
|
|||
|
||||
public MouseEventServerMessage(FriendlyByteBuf buf) {
|
||||
super(buf);
|
||||
type = buf.readByte();
|
||||
type = buf.readEnum(Action.class);
|
||||
arg = buf.readVarInt();
|
||||
x = buf.readVarInt();
|
||||
y = buf.readVarInt();
|
||||
|
@ -41,7 +36,7 @@ public MouseEventServerMessage(FriendlyByteBuf buf) {
|
|||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeByte(type);
|
||||
buf.writeEnum(type);
|
||||
buf.writeVarInt(arg);
|
||||
buf.writeVarInt(x);
|
||||
buf.writeVarInt(y);
|
||||
|
@ -51,10 +46,10 @@ public void write(FriendlyByteBuf buf) {
|
|||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
var input = container.getInput();
|
||||
switch (type) {
|
||||
case TYPE_CLICK -> input.mouseClick(arg, x, y);
|
||||
case TYPE_DRAG -> input.mouseDrag(arg, x, y);
|
||||
case TYPE_UP -> input.mouseUp(arg, x, y);
|
||||
case TYPE_SCROLL -> input.mouseScroll(arg, x, y);
|
||||
case CLICK -> input.mouseClick(arg, x, y);
|
||||
case DRAG -> input.mouseDrag(arg, x, y);
|
||||
case UP -> input.mouseUp(arg, x, y);
|
||||
case SCROLL -> input.mouseScroll(arg, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,4 +57,8 @@ protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
|||
public MessageType<MouseEventServerMessage> type() {
|
||||
return NetworkMessages.MOUSE_EVENT;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
CLICK, DRAG, UP, SCROLL,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,8 +54,8 @@ public boolean pollTerminalChanged() {
|
|||
return terminal;
|
||||
}
|
||||
|
||||
void read(TerminalState state) {
|
||||
if (state.hasTerminal()) {
|
||||
void read(@Nullable TerminalState state) {
|
||||
if (state != null) {
|
||||
if (terminal == null) {
|
||||
terminal = state.create();
|
||||
} else {
|
||||
|
|
|
@ -223,7 +223,7 @@ private void onClientLoad(int oldXIndex, int oldYIndex) {
|
|||
if (xIndex == 0 && yIndex == 0 && clientMonitor == null) clientMonitor = new ClientMonitor(this);
|
||||
}
|
||||
|
||||
public final void read(TerminalState state) {
|
||||
public final void read(@Nullable TerminalState state) {
|
||||
if (xIndex != 0 || yIndex != 0) {
|
||||
LOG.warn("Receiving monitor state for non-origin terminal at {}", getBlockPos());
|
||||
return;
|
||||
|
|
|
@ -68,7 +68,7 @@ public static void onTick() {
|
|||
var state = getState(tile, monitor);
|
||||
ServerNetworking.sendToAllTracking(new MonitorClientMessage(pos, state), chunk);
|
||||
|
||||
limit -= state.size();
|
||||
limit -= state == null ? 0 : state.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,9 +76,9 @@ public static void onTick() {
|
|||
return !monitor.isRemoved() && monitor.getXIndex() == 0 && monitor.getYIndex() == 0 ? monitor.getCachedServerMonitor() : null;
|
||||
}
|
||||
|
||||
private static TerminalState getState(MonitorBlockEntity tile, ServerMonitor monitor) {
|
||||
private static @Nullable TerminalState getState(MonitorBlockEntity tile, ServerMonitor monitor) {
|
||||
var state = tile.cached;
|
||||
if (state == null) state = tile.cached = new TerminalState(monitor.getTerminal());
|
||||
if (state == null) state = tile.cached = TerminalState.create(monitor.getTerminal());
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ public void write(FriendlyByteBuf buf) {
|
|||
buf.writeVarInt(charge());
|
||||
buf.writeVarInt(strength());
|
||||
buf.writeBoolean(previousBit());
|
||||
buf.writeVarInt(audio.remaining());
|
||||
buf.writeBytes(audio().duplicate());
|
||||
}
|
||||
|
||||
|
@ -29,7 +30,8 @@ public static EncodedAudio read(FriendlyByteBuf buf) {
|
|||
var strength = buf.readVarInt();
|
||||
var previousBit = buf.readBoolean();
|
||||
|
||||
var bytes = new byte[buf.readableBytes()];
|
||||
var length = buf.readVarInt();
|
||||
var bytes = new byte[length];
|
||||
buf.readBytes(bytes);
|
||||
|
||||
return new EncodedAudio(charge, strength, previousBit, ByteBuffer.wrap(bytes));
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
@ -88,8 +89,8 @@ protected void unload() {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected double getInteractRange() {
|
||||
return 12.0;
|
||||
protected int getInteractRange() {
|
||||
return Container.DEFAULT_DISTANCE_LIMIT + 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,24 +4,14 @@
|
|||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class BlockEntityHelpers {
|
||||
/**
|
||||
* The maximum limit a player can be away from a block to still have its UI open.
|
||||
*
|
||||
* @see #isUsable(BlockEntity, Player, double)
|
||||
*/
|
||||
public static final double DEFAULT_INTERACT_RANGE = 8.0;
|
||||
|
||||
private BlockEntityHelpers() {
|
||||
}
|
||||
|
||||
|
@ -33,27 +23,6 @@ public static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A
|
|||
return actualType == expectedType ? (BlockEntityTicker<A>) ticker : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a block entity is "usable" by a player.
|
||||
*
|
||||
* @param blockEntity The current block entity.
|
||||
* @param player The player who is trying to interact with the block.
|
||||
* @param range The default distance the player can be away. This typically defaults to {@link #DEFAULT_INTERACT_RANGE},
|
||||
* but a custom value may be used. If {@link PlatformHelper#getReachDistance(Player)} is larger,
|
||||
* that will be used instead.
|
||||
* @return Whether this block entity is usable.
|
||||
*/
|
||||
public static boolean isUsable(BlockEntity blockEntity, Player player, double range) {
|
||||
var level = blockEntity.getLevel();
|
||||
var pos = blockEntity.getBlockPos();
|
||||
|
||||
range = Math.max(range, PlatformHelper.get().getReachDistance(player));
|
||||
|
||||
return player.isAlive() && player.getCommandSenderWorld() == level &&
|
||||
!blockEntity.isRemoved() && level.getBlockEntity(pos) == blockEntity &&
|
||||
player.distanceToSqr(Vec3.atCenterOf(pos)) <= range * range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a block entity, marking it as changed and propagating changes to the client.
|
||||
*
|
||||
|
|
|
@ -52,6 +52,14 @@
|
|||
* an incredibly large file) will fail. You can see a mount's capacity with {@link #getCapacity} and the remaining
|
||||
* space with {@link #getFreeSpace}.
|
||||
*
|
||||
* :::note Case sensitivity
|
||||
* CC inherits the case sensitivity of the real world OS that the Minecraft server is running on, so if you are used to
|
||||
* Microsoft Windows' case insensitivity (e.g. `startup.lua` and `StartUp.Lua` are considered the same when used as
|
||||
* file names) and play on a server (which will likely be running Linux which is case sensitive) then you should keep
|
||||
* in mind that programs which work on your machine in singleplayer may not work correctly on the server if you are
|
||||
* inconsistent with the case that you use for file names.
|
||||
* :::
|
||||
*
|
||||
* @cc.module fs
|
||||
*/
|
||||
public class FSAPI implements ILuaAPI {
|
||||
|
|
Loading…
Reference in New Issue