1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-17 06:57:38 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Jonathan Coates
56d97630e8 Bump CC:T to 1.108.1 2023-09-06 09:51:16 +01:00
Jonathan Coates
e660192f08 Make command computer permission checks stricter
- Placing a command computer requires the player to be in creative and
   opped.
 - Breaking a command computer now requires the player to be opped, as
   well as in creative.

As we've now got a dedicated item class for command comptuers, we move
the command-specific IMedia override to that class.

Fixes #1582.
2023-09-05 18:44:16 +01:00
Jonathan Coates
4e82bd352d Bump the priority of the computer thread
As this is responsible for interrupting computers, we should make sure
its priority is higher than the background threads. It spends most of
its time sleeping, so should be fine.
2023-09-05 18:39:55 +01:00
Jonathan Coates
07113c3e9b Move command actions to their own methods
Rather than having a mess of lambdas, we now move the bulk of the
implemetation to their own methods. The lambdas now just do argument
extraction - it's all stringly typed, so good to keep that with the
argument definition.

This also removes a couple of exception keys (and thus their translation
keys) as we no longer use them.
2023-09-05 18:37:10 +01:00
Jonathan Coates
d562a051c7 Use method handlees in our generated Lua methods (#1579)
When the target method is in a different class loader to CC, our
generated method fails, as it cannot find the target class. To get
around that, we create a MethodHandle to the target method, and then
inject that into the generated class (with Java's new dynamic constant
system). We can then invoke the MethodHandle in our generated code,
avoiding any references to the target class/method.
2023-09-03 16:12:37 +00:00
Jonathan Coates
6ac09742fc Fix errors from the typescript bump
Looks like ./gradlew docWebsite didn't rebuild here.
2023-08-31 20:49:53 +01:00
Jonathan Coates
5dd6b9a637 Generic dependency update
A couple of changes caused by checkstyle being a little more strict.
2023-08-31 19:14:37 +01:00
56 changed files with 1408 additions and 613 deletions

View File

@@ -52,7 +52,7 @@ dependencies {
implementation(libs.forgeGradle)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.quiltflower)
implementation(libs.vineflower)
implementation(libs.vanillaGradle)
}

View File

@@ -12,7 +12,7 @@ import cc.tweaked.gradle.MinecraftConfigurations
plugins {
`java-library`
id("fabric-loom")
id("io.github.juuxel.loom-quiltflower")
id("io.github.juuxel.loom-vineflower")
id("cc-tweaked.java-convention")
}

View File

@@ -191,6 +191,7 @@ spotless {
val ktlintConfig = mapOf(
"ktlint_standard_no-wildcard-imports" to "disabled",
"ktlint_standard_class-naming" to "disabled",
"ij_kotlin_allow_trailing_comma" to "true",
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
)

View File

@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.108.0
modVersion=1.108.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1

View File

@@ -12,12 +12,12 @@ fabric-loader = "0.14.21"
forge = "47.1.0"
forgeSpi = "6.0.0"
mixin = "0.8.5"
parchment = "2023.06.26"
parchmentMc = "1.19.4"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
# Normal dependencies
asm = "9.3"
autoService = "1.0.1"
asm = "9.5"
autoService = "1.1.1"
checkerFramework = "3.32.0"
cobalt = "0.7.3"
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
@@ -29,7 +29,7 @@ jzlib = "1.1.3"
kotlin = "1.8.10"
kotlin-coroutines = "1.6.4"
netty = "4.1.82.Final"
nightConfig = "3.6.5"
nightConfig = "3.6.7"
slf4j = "1.7.36"
# Minecraft mods
@@ -45,30 +45,30 @@ rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
# Testing
byteBuddy = "1.14.2"
byteBuddy = "1.14.7"
hamcrest = "2.2"
jqwik = "1.7.2"
junit = "5.9.2"
jqwik = "1.7.4"
junit = "5.10.0"
# Build tools
cctJavadoc = "1.8.0"
checkstyle = "10.3.4"
checkstyle = "10.12.3"
curseForgeGradle = "1.0.14"
errorProne-core = "2.18.0"
errorProne-plugin = "3.0.1"
errorProne-core = "2.21.1"
errorProne-plugin = "3.1.0"
fabric-loom = "1.3.7"
forgeGradle = "6.0.8"
githubRelease = "2.2.12"
ideaExt = "1.1.6"
githubRelease = "2.4.1"
ideaExt = "1.1.7"
illuaminate = "0.1.0-40-g975cbc3"
librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
quiltflower = "1.10.0"
spotless = "6.17.0"
spotless = "6.21.0"
taskTree = "2.1.1"
vanillaGradle = "0.2.1-SNAPSHOT"
vineflower = "1.11.0"
[libraries]
# Normal dependencies
@@ -137,9 +137,9 @@ kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vineflower" }
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }

1228
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,15 +15,15 @@
"@rollup/plugin-url": "^8.0.1",
"@types/glob": "^8.1.0",
"@types/react-dom": "^18.0.5",
"glob": "^9.3.0",
"glob": "^10.3.4",
"react-dom": "^18.1.0",
"react": "^18.1.0",
"rehype-highlight": "^6.0.0",
"rehype-react": "^7.1.1",
"rehype": "^12.0.1",
"rehype": "^12.0.0",
"requirejs": "^2.3.6",
"rollup": "^3.19.1",
"ts-node": "^10.8.0",
"typescript": "^4.0.5"
"typescript": "^5.2.2"
}
}

View File

@@ -17,7 +17,7 @@ import org.joml.Matrix4f;
import javax.annotation.Nullable;
class TurtleUpgradeModellers {
final class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
private static final Transformation rightTransform = getMatrixFor(0.4065f);
@@ -35,7 +35,7 @@ class TurtleUpgradeModellers {
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);

View File

@@ -1,6 +1,6 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: LicenseRef-CCPL
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.gui;

View File

@@ -47,7 +47,7 @@ public class ShaderMod {
Optional<ShaderMod> get();
}
private static class Storage {
private static final class Storage {
static final ShaderMod INSTANCE = ServiceLoader.load(Provider.class)
.stream()
.flatMap(x -> x.get().get().stream())

View File

@@ -139,8 +139,6 @@ public final class LanguageProvider implements DataProvider {
add("commands.computercraft.tp.synopsis", "Teleport to a specific computer.");
add("commands.computercraft.tp.desc", "Teleport to the location of a computer. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).");
add("commands.computercraft.tp.action", "Teleport to this computer");
add("commands.computercraft.tp.not_player", "Cannot open terminal for non-player");
add("commands.computercraft.tp.not_there", "Cannot locate computer in the world");
add("commands.computercraft.view.synopsis", "View the terminal of a computer.");
add("commands.computercraft.view.desc", "Open the terminal of a computer, allowing remote control of a computer. This does not provide access to turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).");
add("commands.computercraft.view.action", "View this computer");

View File

@@ -24,7 +24,7 @@ final class WiredNetworkImpl implements WiredNetwork {
nodes.add(node);
}
private WiredNetworkImpl(HashSet<WiredNodeImpl> nodes) {
private WiredNetworkImpl(Set<WiredNodeImpl> nodes) {
this.nodes = nodes;
}
@@ -375,7 +375,7 @@ final class WiredNetworkImpl implements WiredNetwork {
}
}
private static HashSet<WiredNodeImpl> reachableNodes(WiredNodeImpl start) {
private static Set<WiredNodeImpl> reachableNodes(WiredNodeImpl start) {
Queue<WiredNodeImpl> enqueued = new ArrayDeque<>();
var reachable = new HashSet<WiredNodeImpl>();

View File

@@ -24,12 +24,14 @@ import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.CommandComputerBlockEntity;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
@@ -139,7 +141,7 @@ public final class ModRegistry {
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new ComputerBlock<>(
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new CommandComputerBlock<>(
computerProperties().strength(-1, 6000000.0F),
ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
));
@@ -222,7 +224,7 @@ public final class ModRegistry {
public static final RegistryEntry<ComputerItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, ComputerItem::new);
public static final RegistryEntry<ComputerItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, ComputerItem::new);
public static final RegistryEntry<ComputerItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, ComputerItem::new);
public static final RegistryEntry<ComputerItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, CommandComputerItem::new);
public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = REGISTRY.register("pocket_computer_normal",
() -> new PocketComputerItem(properties().stacksTo(1), ComputerFamily.NORMAL));

View File

@@ -26,7 +26,6 @@ import dan200.computercraft.shared.network.container.ComputerContainerData;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.entity.player.Inventory;
@@ -34,13 +33,15 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.io.File;
import java.util.*;
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
import static dan200.computercraft.shared.command.Exceptions.*;
import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION;
import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*;
@@ -62,118 +63,25 @@ public final class CommandComputerCraft {
dispatcher.register(choice("computercraft")
.then(literal("dump")
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
.executes(context -> {
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
var source = context.getSource();
List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
Level world = source.getLevel();
var pos = BlockPos.containing(source.getPosition());
computers.sort((a, b) -> {
if (a.getLevel() == b.getLevel() && a.getLevel() == world) {
return Double.compare(a.getPosition().distSqr(pos), b.getPosition().distSqr(pos));
} else if (a.getLevel() == world) {
return -1;
} else if (b.getLevel() == world) {
return 1;
} else {
return Integer.compare(a.getInstanceID(), b.getInstanceID());
}
});
for (var computer : computers) {
table.row(
linkComputer(source, computer, computer.getID()),
bool(computer.isOn()),
linkPosition(source, computer)
);
}
table.display(context.getSource());
return computers.size();
})
.executes(c -> dump(c.getSource()))
.then(args()
.arg("computer", oneComputer())
.executes(context -> {
var computer = getComputerArgument(context, "computer");
var table = new TableBuilder("Dump");
table.row(header("Instance"), text(Integer.toString(computer.getInstanceID())));
table.row(header("Id"), text(Integer.toString(computer.getID())));
table.row(header("Label"), text(computer.getLabel()));
table.row(header("On"), bool(computer.isOn()));
table.row(header("Position"), linkPosition(context.getSource(), computer));
table.row(header("Family"), text(computer.getFamily().toString()));
for (var side : ComputerSide.values()) {
var peripheral = computer.getPeripheral(side);
if (peripheral != null) {
table.row(header("Peripheral " + side.getName()), text(peripheral.getType()));
}
}
table.display(context.getSource());
return 1;
})))
.executes(c -> dumpComputer(c.getSource(), getComputerArgument(c, "computer")))))
.then(command("shutdown")
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
.executes((context, computerSelectors) -> {
var shutdown = 0;
var computers = unwrap(context.getSource(), computerSelectors);
for (var computer : computers) {
if (computer.isOn()) shutdown++;
computer.shutdown();
}
var didShutdown = shutdown;
context.getSource().sendSuccess(() -> Component.translatable("commands.computercraft.shutdown.done", didShutdown, computers.size()), false);
return shutdown;
}))
.executes((c, a) -> shutdown(c.getSource(), unwrap(c.getSource(), a))))
.then(command("turn-on")
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
.executes((context, computerSelectors) -> {
var on = 0;
var computers = unwrap(context.getSource(), computerSelectors);
for (var computer : computers) {
if (!computer.isOn()) on++;
computer.turnOn();
}
var didOn = on;
context.getSource().sendSuccess(() -> Component.translatable("commands.computercraft.turn_on.done", didOn, computers.size()), false);
return on;
}))
.executes((c, a) -> turnOn(c.getSource(), unwrap(c.getSource(), a))))
.then(command("tp")
.requires(ModRegistry.Permissions.PERMISSION_TP)
.arg("computer", oneComputer())
.executes(context -> {
var computer = getComputerArgument(context, "computer");
var world = computer.getLevel();
var pos = computer.getPosition();
var entity = context.getSource().getEntityOrException();
if (!(entity instanceof ServerPlayer player)) throw TP_NOT_PLAYER.create();
if (player.getCommandSenderWorld() == world) {
player.connection.teleport(
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0,
EnumSet.noneOf(RelativeMovement.class)
);
} else {
player.teleportTo(world,
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0
);
}
return 1;
}))
.executes(c -> teleport(c.getSource(), getComputerArgument(c, "computer"))))
.then(command("queue")
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
@@ -182,79 +90,243 @@ public final class CommandComputerCraft {
.suggests((context, builder) -> Suggestions.empty())
)
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
.executes((ctx, args) -> {
var computers = getComputersArgument(ctx, "computer");
var rest = args.toArray();
var queued = 0;
for (var computer : computers) {
if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
computer.queueEvent("computer_command", rest);
queued++;
}
}
return queued;
}))
.executes((c, a) -> queue(getComputersArgument(c, "computer"), a)))
.then(command("view")
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
.arg("computer", oneComputer())
.executes(context -> {
var player = context.getSource().getPlayerOrException();
var computer = getComputerArgument(context, "computer");
new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
return new ViewComputerMenu(id, player, computer);
}
});
return 1;
}))
.executes(c -> view(c.getSource(), getComputerArgument(c, "computer"))))
.then(choice("track")
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
.then(command("start")
.executes(context -> {
getMetricsInstance(context.getSource()).start();
var stopCommand = "/computercraft track stop";
context.getSource().sendSuccess(() -> Component.translatable(
"commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
), false);
return 1;
}))
.then(command("stop")
.executes(context -> {
var timings = getMetricsInstance(context.getSource());
if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
displayTimings(context.getSource(), timings.getSnapshot(), new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG), DEFAULT_FIELDS);
return 1;
}))
.then(command("start").executes(c -> trackStart(c.getSource())))
.then(command("stop").executes(c -> trackStop(c.getSource())))
.then(command("dump")
.argManyValue("fields", metric(), DEFAULT_FIELDS)
.executes((context, fields) -> {
AggregatedMetric sort;
if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get(0))) {
sort = fields.get(0);
fields = DEFAULT_FIELDS;
} else {
sort = fields.get(0);
}
return displayTimings(context.getSource(), sort, fields);
})))
.executes((c, f) -> trackDump(c.getSource(), f))))
);
}
/**
* Display loaded computers to a table.
*
* @param source The thing that executed this command.
* @return The number of loaded computers.
*/
private static int dump(CommandSourceStack source) {
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
Level world = source.getLevel();
var pos = BlockPos.containing(source.getPosition());
// Sort by nearby computers.
computers.sort((a, b) -> {
if (a.getLevel() == b.getLevel() && a.getLevel() == world) {
return Double.compare(a.getPosition().distSqr(pos), b.getPosition().distSqr(pos));
} else if (a.getLevel() == world) {
return -1;
} else if (b.getLevel() == world) {
return 1;
} else {
return Integer.compare(a.getInstanceID(), b.getInstanceID());
}
});
for (var computer : computers) {
table.row(
linkComputer(source, computer, computer.getID()),
bool(computer.isOn()),
linkPosition(source, computer)
);
}
table.display(source);
return computers.size();
}
/**
* Display additional information about a single computer.
*
* @param source The thing that executed this command.
* @param computer The computer we're dumping.
* @return The constant {@code 1}.
*/
private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
var table = new TableBuilder("Dump");
table.row(header("Instance"), text(Integer.toString(computer.getInstanceID())));
table.row(header("Id"), text(Integer.toString(computer.getID())));
table.row(header("Label"), text(computer.getLabel()));
table.row(header("On"), bool(computer.isOn()));
table.row(header("Position"), linkPosition(source, computer));
table.row(header("Family"), text(computer.getFamily().toString()));
for (var side : ComputerSide.values()) {
var peripheral = computer.getPeripheral(side);
if (peripheral != null) {
table.row(header("Peripheral " + side.getName()), text(peripheral.getType()));
}
}
table.display(source);
return 1;
}
/**
* Shutdown a list of computers.
*
* @param source The thing that executed this command.
* @param computers The computers to shutdown.
* @return The constant {@code 1}.
*/
private static int shutdown(CommandSourceStack source, Collection<ServerComputer> computers) {
var shutdown = 0;
for (var computer : computers) {
if (computer.isOn()) shutdown++;
computer.shutdown();
}
var didShutdown = shutdown;
source.sendSuccess(() -> Component.translatable("commands.computercraft.shutdown.done", didShutdown, computers.size()), false);
return shutdown;
}
/**
* Turn on a list of computers.
*
* @param source The thing that executed this command.
* @param computers The computers to turn on.
* @return The constant {@code 1}.
*/
private static int turnOn(CommandSourceStack source, Collection<ServerComputer> computers) {
var on = 0;
for (var computer : computers) {
if (!computer.isOn()) on++;
computer.turnOn();
}
var didOn = on;
source.sendSuccess(() -> Component.translatable("commands.computercraft.turn_on.done", didOn, computers.size()), false);
return on;
}
/**
* Teleport to a computer.
*
* @param source The thing that executed this command. This must be an entity, other types will throw an exception.
* @param computer The computer to teleport to.
* @return The constant {@code 1}.
*/
private static int teleport(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
var world = computer.getLevel();
var pos = Vec3.atBottomCenterOf(computer.getPosition());
source.getEntityOrException().teleportTo(world, pos.x(), pos.y(), pos.z(), EnumSet.noneOf(RelativeMovement.class), 0, 0);
return 1;
}
/**
* Queue a {@code computer_command} event on a command computer.
*
* @param computers The list of computers to queue on.
* @param args The arguments for this event.
* @return The number of computers this event was queued on.
*/
private static int queue(Collection<ServerComputer> computers, List<String> args) {
var rest = args.toArray();
var queued = 0;
for (var computer : computers) {
if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
computer.queueEvent("computer_command", rest);
queued++;
}
}
return queued;
}
/**
* Open a terminal for a computer.
*
* @param source The thing that executed this command.
* @param computer The computer to view.
* @return The constant {@code 1}.
*/
private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
var player = source.getPlayerOrException();
new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
return new ViewComputerMenu(id, player, computer);
}
});
return 1;
}
/**
* Start tracking metrics for the current player.
*
* @param source The thing that executed this command.
* @return The constant {@code 1}.
*/
private static int trackStart(CommandSourceStack source) {
getMetricsInstance(source).start();
var stopCommand = "/computercraft track stop";
source.sendSuccess(() -> Component.translatable(
"commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
), false);
return 1;
}
/**
* Stop tracking metrics for the current player, displaying a table with the results.
*
* @param source The thing that executed this command.
* @return The constant {@code 1}.
*/
private static int trackStop(CommandSourceStack source) throws CommandSyntaxException {
var metrics = getMetricsInstance(source);
if (!metrics.stop()) throw NOT_TRACKING_EXCEPTION.create();
displayTimings(source, metrics.getSnapshot(), new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG), DEFAULT_FIELDS);
return 1;
}
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
);
/**
* Display the latest metrics for the current player.
*
* @param source The thing that executed this command.
* @param fields The fields to display in this table, defaulting to {@link #DEFAULT_FIELDS}.
* @return The constant {@code 1}.
*/
private static int trackDump(CommandSourceStack source, List<AggregatedMetric> fields) throws CommandSyntaxException {
AggregatedMetric sort;
if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get(0))) {
sort = fields.get(0);
fields = DEFAULT_FIELDS;
} else {
sort = fields.get(0);
}
return displayTimings(source, getMetricsInstance(source).getTimings(), sort, fields);
}
// Additional helper functions.
private static Component linkComputer(CommandSourceStack source, @Nullable ServerComputer serverComputer, int computerId) {
var out = Component.literal("");
@@ -327,16 +399,6 @@ public final class CommandComputerCraft {
return ServerContext.get(source.getServer()).metrics().getMetricsInstance(entity instanceof Player ? entity.getUUID() : SYSTEM_UUID);
}
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
);
private static int displayTimings(CommandSourceStack source, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
return displayTimings(source, getMetricsInstance(source).getTimings(), sortField, fields);
}
private static int displayTimings(CommandSourceStack source, List<ComputerMetrics> timings, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
if (timings.isEmpty()) throw NO_TIMINGS_EXCEPTION.create();

View File

@@ -18,9 +18,6 @@ public final class Exceptions {
static final SimpleCommandExceptionType NOT_TRACKING_EXCEPTION = translated("commands.computercraft.track.stop.not_enabled");
static final SimpleCommandExceptionType NO_TIMINGS_EXCEPTION = translated("commands.computercraft.track.dump.no_timings");
static final SimpleCommandExceptionType TP_NOT_THERE = translated("commands.computercraft.tp.not_there");
static final SimpleCommandExceptionType TP_NOT_PLAYER = translated("commands.computercraft.tp.not_player");
public static final SimpleCommandExceptionType ARGUMENT_EXPECTED = translated("argument.computercraft.argument_expected");
private static SimpleCommandExceptionType translated(String key) {

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
/**
* A subclass of {@link ComputerBlock} which implements {@link GameMasterBlock}, to prevent players breaking it without
* permission.
*
* @param <T> The type of the computer block entity.
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
*/
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
public CommandComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
super(settings, family, type);
}
}

View File

@@ -104,11 +104,15 @@ public class CommandComputerBlockEntity extends ComputerBlockEntity {
if (server == null || !server.isCommandBlockEnabled()) {
player.displayClientMessage(Component.translatable("advMode.notEnabled"), true);
return false;
} else if (Config.commandRequireCreative ? !player.canUseGameMasterBlocks() : !server.getPlayerList().isOp(player.getGameProfile())) {
} else if (!canUseCommandBlock(player)) {
player.displayClientMessage(Component.translatable("advMode.notAllowed"), true);
return false;
}
return true;
}
private static boolean canUseCommandBlock(Player player) {
return Config.commandRequireCreative ? player.canUseGameMasterBlocks() : player.hasPermissions(2);
}
}

View File

@@ -64,13 +64,7 @@ public abstract class AbstractComputerItem extends BlockItem implements ICompute
@Override
public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) {
var family = getFamily();
if (family != ComputerFamily.COMMAND) {
var id = getComputerID(stack);
if (id >= 0) {
return ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id, Config.computerSpaceLimit);
}
}
return null;
var id = getComputerID(stack);
return id >= 0 ? ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id, Config.computerSpaceLimit) : null;
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
/**
* A {@link ComputerItem} which prevents players placing it without permission.
*
* @see net.minecraft.world.item.GameMasterBlockItem
* @see dan200.computercraft.shared.computer.blocks.CommandComputerBlock
*/
public class CommandComputerItem extends ComputerItem {
public CommandComputerItem(ComputerBlock<?> block, Properties settings) {
super(block, settings);
}
@Override
protected @Nullable BlockState getPlacementState(BlockPlaceContext context) {
// Prohibit players placing this block in survival or when not opped.
var player = context.getPlayer();
return player != null && !player.canUseGameMasterBlocks() ? null : super.getPlacementState(context);
}
@Override
public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) {
// Don't allow command computers to be mounted in disk drives.
return null;
}
}

View File

@@ -141,7 +141,7 @@ public final class ComputerMBean implements DynamicMBean, ComputerMetricsObserve
}
}
private static class Counter {
private static final class Counter {
final AtomicLong value = new AtomicLong();
final AtomicLong count = new AtomicLong();
}

View File

@@ -116,6 +116,7 @@ public class ItemDetails {
* @param enchants The enchantment map to add it to.
* @see EnchantmentHelper
*/
@SuppressWarnings("NonApiType")
private static void addEnchantments(ListTag rawEnchants, ArrayList<Map<String, Object>> enchants) {
if (rawEnchants.isEmpty()) return;

View File

@@ -69,7 +69,7 @@ public abstract class PermissionRegistry {
.orElseGet(DefaultPermissionRegistry::new);
}
private static class DefaultPermissionRegistry extends PermissionRegistry {
private static final class DefaultPermissionRegistry extends PermissionRegistry {
@Override
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
checkNotFrozen();

View File

@@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference;
public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
private static final String NBT_ITEM = "Item";
private static class MountInfo {
private static final class MountInfo {
@Nullable
String mountPath;
}

View File

@@ -36,7 +36,7 @@ import java.util.Collections;
public class CableBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
private class CableElement extends WiredModemElement {
private final class CableElement extends WiredModemElement {
@Override
public Level getLevel() {
return CableBlockEntity.this.getLevel();

View File

@@ -239,7 +239,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
world.sendBlockUpdated(tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), Block.UPDATE_ALL);
}
private static class ErrorMessage {
private static final class ErrorMessage {
@Nullable
String message;
}

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Různé příkazy pro ovládání počítačů.",
"commands.computercraft.tp.action": "Teleportovat se k počítači",
"commands.computercraft.tp.desc": "Teleportovat se na místo počítače. Můžeš specifikovat ID počítačové instance (tř. 123) nebo ID počítače (tř. #123).",
"commands.computercraft.tp.not_player": "Nelze otevřít terminál pro nehráče",
"commands.computercraft.tp.not_there": "Nelze najít počítač ve světě",
"commands.computercraft.tp.synopsis": "Teleportovat se ke specifickému počítači.",
"commands.computercraft.track.desc": "Sledovat jak dlouho se počítače spustí, a také kolik událostí zpracují. Toto uvádí informace v podobné cestě jako /forge track a může být dobré pro diagnostiku lagu.",
"commands.computercraft.track.dump.computer": "Počítač",

View File

@@ -46,8 +46,6 @@
"commands.computercraft.synopsis": "Verschiedene Befehle um Computer zu kontrollieren.",
"commands.computercraft.tp.action": "Teleportiert dich zum Computer",
"commands.computercraft.tp.desc": "Teleportiert dich zum Standort eines Computers. Der Computer kann entweder über seine Instanz ID (z.B. 123), seine Computer ID (z.B. #123) oder seinen Namen (z.B. \"@Mein Computer\") angegeben werden.",
"commands.computercraft.tp.not_player": "Konnte Terminal für Nicht-Spieler nicht öffnen",
"commands.computercraft.tp.not_there": "Konnte Computer in der Welt nicht finden",
"commands.computercraft.tp.synopsis": "Teleportiert dich zum angegebenen Computer.",
"commands.computercraft.track.desc": "Zeichnet die Laufzeiten von Computern und wie viele Events ausgelöst werden auf. Die Ausgabe der Informationen ist ähnlich zu /forge track, was beim aufspüren von Lags sehr hilfreich sein kann.",
"commands.computercraft.track.dump.computer": "Computer",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Commandes diverses pour contrôler les ordinateurs.",
"commands.computercraft.tp.action": "Se téléporter vers cet ordinateur",
"commands.computercraft.tp.desc": "Se téléporter à la position de l'ordinateur. Vous pouvez spécifier l'identifiant d'instance (ex. 123) ou l'identifiant d'ordinateur (ex. #123).",
"commands.computercraft.tp.not_player": "Impossible d'ouvrir un terminal pour un non-joueur",
"commands.computercraft.tp.not_there": "Impossible de localiser cet ordinateur dans le monde",
"commands.computercraft.tp.synopsis": "Se téléporter à la position de l'ordinateur spécifié.",
"commands.computercraft.track.desc": "Surveillez combien de temps prend une exécutions sur les ordinateurs, ainsi que le nombre dévénements capturés. Les informations sont affichées d'une manière similaire à la commande /forge track, utile pour diagnostiquer les sources de latence.",
"commands.computercraft.track.dump.computer": "Ordinateur",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Vari comandi per controllare i computer.",
"commands.computercraft.tp.action": "Teletrasporta a questo computer",
"commands.computercraft.tp.desc": "Teletrasporta alla posizione di un computer. Puoi specificare il computer con l'instance id (e.g. 123) o con l'id (e.g. #123).",
"commands.computercraft.tp.not_player": "Non è possibile aprire un terminale per un non giocatore",
"commands.computercraft.tp.not_there": "Impossibile trovare il computer nel mondo",
"commands.computercraft.tp.synopsis": "Teletrasporta al computer specificato.",
"commands.computercraft.track.desc": "Monitora per quanto tempo i computer vengono eseguiti e quanti eventi ricevono. Questo comando fornisce le informazioni in modo simile a /forge track e può essere utile per diagnosticare il lag.",
"commands.computercraft.track.dump.computer": "Computer",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "コンピュータを制御するためのさまざまなコマンド。",
"commands.computercraft.tp.action": "このコンピューターへテレポートします",
"commands.computercraft.tp.desc": "コンピュータの場所にテレポート.コンピュータのインスタンスID例えば 123またはコンピュータID例えば #123を指定することができます。",
"commands.computercraft.tp.not_player": "非プレイヤー用のターミナルを開くことができません",
"commands.computercraft.tp.not_there": "世界でコンピュータを見つけることができませんでした",
"commands.computercraft.tp.synopsis": "特定のコンピュータにテレポート。",
"commands.computercraft.track.desc": "コンピュータの実行時間を追跡するだけでなく、イベントを確認することができます。 これは /forge と同様の方法で情報を提示し、遅れを診断するのに役立ちます。",
"commands.computercraft.track.dump.computer": "コンピューター",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "컴퓨터를 제어하기 위한 다양한 명령어",
"commands.computercraft.tp.action": "이 컴퓨터로 순간이동하기",
"commands.computercraft.tp.desc": "컴퓨터의 위치로 순간이동합니다. 컴퓨터의 인스턴스 ID(예: 123) 또는 컴퓨터 ID(예: #123)를 지정할 수 있습니다.",
"commands.computercraft.tp.not_player": "비플레이어용 터미널을 열 수 없습니다.",
"commands.computercraft.tp.not_there": "월드에서 컴퓨터를 위치시킬 수 없습니다.",
"commands.computercraft.tp.synopsis": "특정 컴퓨터로 순간이동하기",
"commands.computercraft.track.desc": "컴퓨터가 실행되는 기간과 처리되는 이벤트 수를 추적합니다. 이는 /forge 트랙과 유사한 방법으로 정보를 제공하며 지연 로그에 유용할 수 있습니다.",
"commands.computercraft.track.dump.computer": "컴퓨터",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Forskjellige kommandoer for å kontrollere datamaskiner.",
"commands.computercraft.tp.action": "Teleporter til denne datamaskinen",
"commands.computercraft.tp.desc": "Teleporter til en datamaskin sin posisjon. Du kan enten spesifisere datamaskinens forekomst id (f. eks 123) eller datamaskinens id (f. eks #123).",
"commands.computercraft.tp.not_player": "Kan kun åpne terminalen for spillere",
"commands.computercraft.tp.not_there": "Greide ikke å finne datamaskinen i denne verdenen",
"commands.computercraft.tp.synopsis": "Teleporter til en spesifikk datamaskin.",
"commands.computercraft.track.desc": "Spor hvor lenge datamaskiner kjører, samt hvor mange hendelser de handler. Dette presenterer informasjon i en lignende vei til /forge track og kan være nyttig for å diagnostisere lagg.",
"commands.computercraft.track.dump.computer": "Datamaskin",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Verschillende commando's voor het beheren van computers.",
"commands.computercraft.tp.action": "Teleporteer naar deze computer",
"commands.computercraft.tp.desc": "Teleporteer naar de locatie van een specefieke computer. Je kunt een instance-id (bijv. 123) of computer-id (bijv. #123) opgeven.",
"commands.computercraft.tp.not_player": "Kan geen terminal openen voor non-speler",
"commands.computercraft.tp.not_there": "Kan de computer niet lokaliseren",
"commands.computercraft.tp.synopsis": "Teleporteer naar een specifieke computer.",
"commands.computercraft.track.desc": "Houd uitvoertijd en het aantal behandelde events van computers bij. Dit biedt informatie op een gelijke manier als /forge track en kan nuttig zijn in het opsloren van lag.",
"commands.computercraft.track.dump.computer": "Computer",

View File

@@ -31,7 +31,6 @@
"commands.computercraft.synopsis": "Różne komendy do kontrolowania komputerami.",
"commands.computercraft.tp.action": "Przeteleportuj się do podanego komputera",
"commands.computercraft.tp.desc": "Przeteleportuj się do lokalizacji komputera. Możesz wybrać numer sesji komputera (np. 123) lub ID komputera (np. #123).",
"commands.computercraft.tp.not_there": "Nie można zlokalizować komputera w świecie",
"commands.computercraft.tp.synopsis": "Przeteleportuj się do podanego komputera.",
"commands.computercraft.track.dump.computer": "Komputer",
"commands.computercraft.turn_on.desc": "Włącz podane komputery. Możesz wybrać numer sesji komputera (np. 123), ID komputera (np. #123) lub jego etykietę (np. \"@Mój Komputer\").",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Различные команды для управления компьютерами.",
"commands.computercraft.tp.action": "Телепортироваться к этому компьютеру",
"commands.computercraft.tp.desc": "Телепортироваться к местоположению компьютера. Ты можешь указать либо идентификатор экземпляра компьютера (например 123) либо идентификатор компьютера (например #123).",
"commands.computercraft.tp.not_player": "Нельзя открыть терминал для не-игрока",
"commands.computercraft.tp.not_there": "Нельзя определить в мире местоположение компьютер",
"commands.computercraft.tp.synopsis": "Телепортироваться к конкретному компьютеру.",
"commands.computercraft.track.desc": "Отслеживает, как долго компьютеры исполняют, а также то, как много они обрабатывают события. Эта информация представляется аналогично к /forge track и может быть полезной для диагностики лага.",
"commands.computercraft.track.dump.computer": "Компьютер",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Olika kommandon för att kontrollera datorer.",
"commands.computercraft.tp.action": "Teleportera till den här datorn",
"commands.computercraft.tp.desc": "Teleportera till datorns position. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
"commands.computercraft.tp.not_player": "Kan inte öppna terminalen för en ickespelare",
"commands.computercraft.tp.not_there": "Kan inte hitta datorn i världen",
"commands.computercraft.tp.synopsis": "Teleportera till en specifik dator.",
"commands.computercraft.track.desc": "Spåra hur länge datorer exekverar, och även hur många event de hanterar. Detta presenterar information på liknande sätt som /forge track och kan vara användbart för att undersöka lagg.",
"commands.computercraft.track.dump.computer": "Dator",

View File

@@ -44,8 +44,6 @@
"commands.computercraft.synopsis": "mi jo e toki wawa ante tawa ni: sina lawa e ilo sona.",
"commands.computercraft.tp.action": "o tawa pi ilo sona",
"commands.computercraft.tp.desc": "o tawa ilo sona. ilo sona la, sina ken pana e nanpa pi ijo (sama 123) anu nanpa (sama #123).",
"commands.computercraft.tp.not_player": "jan ala la, mi ken ala open e sitelen pi ilo sona",
"commands.computercraft.tp.not_there": "mi ken ala alasa e ilo sona pi lon ma",
"commands.computercraft.tp.synopsis": "mi tawa pi ilo sona e sina.",
"commands.computercraft.track.desc": "ilo sona la, mi sitelen e tenpo pali e mute toki. toki wawa /forge track la, mi pana pi nasin sama e sona. mi pona tawa alasa pi tenpo ike.",
"commands.computercraft.track.dump.computer": "ilo sona",

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Різні команди для керування комп'ютерами.",
"commands.computercraft.tp.action": "Телепортувати до цього комп'ютера",
"commands.computercraft.tp.desc": "Телепортувати до розташування комп'ютера. Ви можете вказати або ідентифікатор екземпляра комп'ютера (наприклад, 123) або ідентифікатор комп'ютера (наприклад, #123).",
"commands.computercraft.tp.not_player": "Не можна відкрити термінал для не-гравця",
"commands.computercraft.tp.not_there": "Не можна визначити у світі розташування комп'ютер",
"commands.computercraft.tp.synopsis": "Телепортувати до конкретного комп'ютера.",
"commands.computercraft.track.desc": "Відстежує, як довго комп'ютери виконують, а також те, як багато вони обробляють події. Ця інформація представляється аналогічно /forge track і може бути корисною для діагностики лага.",
"commands.computercraft.track.dump.computer": "Комп'ютер",

View File

@@ -45,8 +45,6 @@
"commands.computercraft.synopsis": "各种控制计算机的命令.",
"commands.computercraft.tp.action": "传送到这台电脑",
"commands.computercraft.tp.desc": "传送到计算机的位置. 你可以指定计算机的实例id (例如. 123)或计算机id (例如. #123).",
"commands.computercraft.tp.not_player": "无法为非玩家打开终端",
"commands.computercraft.tp.not_there": "无法在世界上定位电脑",
"commands.computercraft.tp.synopsis": "传送到特定的计算机.",
"commands.computercraft.track.desc": "跟踪计算机执行的时间以及它们处理的事件数. 这以/forge track类似的方式呈现信息可用于诊断滞后.",
"commands.computercraft.track.dump.computer": "计算机",

View File

@@ -353,7 +353,7 @@ public class NetworkTest {
}
}
private static class NetworkPeripheral implements IPeripheral {
private static final class NetworkPeripheral implements IPeripheral {
@Override
public String getType() {
return "test";

View File

@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.asm;
import java.security.ProtectionDomain;
final class DeclaringClassLoader extends ClassLoader {
static final DeclaringClassLoader INSTANCE = new DeclaringClassLoader();
private DeclaringClassLoader() {
super(DeclaringClassLoader.class.getClassLoader());
}
Class<?> define(String name, byte[] bytes, ProtectionDomain protectionDomain) throws ClassFormatError {
return defineClass(name, bytes, 0, bytes.length, protectionDomain);
}
}

View File

@@ -10,26 +10,44 @@ import com.google.common.cache.LoadingCache;
import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
import dan200.computercraft.api.lua.*;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import dan200.computercraft.core.methods.LuaMethod;
import org.objectweb.asm.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import static org.objectweb.asm.Opcodes.*;
/**
* The underlying generator for {@link LuaFunction}-annotated methods.
* <p>
* The constructor {@link Generator#Generator(Class, List, Function)} takes in the type of interface to generate (i.e.
* {@link LuaMethod}), the context arguments for this function (in the case of {@link LuaMethod}, this will just be
* {@link ILuaContext}) and a "wrapper" function to lift a function to execute on the main thread.
* <p>
* The generated class then implements this interface - the {@code apply} method calls the appropriate methods on
* {@link IArguments} to extract the arguments, and then calls the original method.
* <p>
* As the method is not guaranteed to come from the same classloader, we cannot call the method directly, as that may
* result in linkage errors. We instead inject a {@link MethodHandle} into the class as a dynamic constant, and then
* call the method with {@link MethodHandle#invokeExact(Object...)}. The method handle is constant, and so this has
* equivalent performance to the direct call.
*
* @param <T> The type of the interface the generated classes implement.
*/
final class Generator<T> {
private static final Logger LOG = LoggerFactory.getLogger(Generator.class);
private static final AtomicInteger METHOD_ID = new AtomicInteger();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final String METHOD_NAME = "apply";
private static final String[] EXCEPTIONS = new String[]{ Type.getInternalName(LuaException.class) };
@@ -42,11 +60,17 @@ final class Generator<T> {
private static final String INTERNAL_COERCED = Type.getInternalName(Coerced.class);
private static final ConstantDynamic METHOD_CONSTANT = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), new Handle(
H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class).descriptorString(), false
));
private final Class<T> base;
private final List<Class<?>> context;
private final String[] interfaces;
private final String methodDesc;
private final String classPrefix;
private final Function<T, T> wrap;
@@ -64,6 +88,8 @@ final class Generator<T> {
for (var klass : context) methodDesc.append(Type.getDescriptor(klass));
methodDesc.append(DESC_ARGUMENTS).append(")").append(DESC_METHOD_RESULT);
this.methodDesc = methodDesc.toString();
classPrefix = Generator.class.getPackageName() + "." + base.getSimpleName() + "$";
}
Optional<T> getMethod(Method method) {
@@ -110,11 +136,17 @@ final class Generator<T> {
var target = Modifier.isStatic(modifiers) ? method.getParameterTypes()[0] : method.getDeclaringClass();
try {
var className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement();
var bytes = generate(className, target, method, annotation.unsafe());
var handle = LOOKUP.unreflect(method);
// Convert the handle from one of the form (target, ...) -> ret type to (Object, ...) -> Object. This both
// handles the boxing of primitives for us, and ensures our bytecode does not reference any external types.
// We could handle the conversion to MethodResult here too, but it doesn't feel worth it.
var widenedHandle = handle.asType(widenMethodType(handle.type(), target));
var bytes = generate(classPrefix + method.getName(), target, method, widenedHandle.type().descriptorString(), annotation.unsafe());
if (bytes == null) return Optional.empty();
var klass = DeclaringClassLoader.INSTANCE.define(className, bytes, method.getDeclaringClass().getProtectionDomain());
var klass = LOOKUP.defineHiddenClassWithClassData(bytes, widenedHandle, true).lookupClass();
var instance = klass.asSubclass(base).getDeclaredConstructor().newInstance();
return Optional.of(annotation.mainThread() ? wrap.apply(instance) : instance);
@@ -122,16 +154,29 @@ final class Generator<T> {
LOG.error("Error generating wrapper for {}.", name, e);
return Optional.empty();
}
}
private static MethodType widenMethodType(MethodType source, Class<?> target) {
// Treat the target argument as just Object - we'll do the cast in the method handle.
var args = source.parameterArray();
for (var i = 0; i < args.length; i++) {
if (args[i] == target) args[i] = Object.class;
}
// And convert the return value to Object if needed.
var ret = source.returnType();
return ret == void.class || ret == MethodResult.class || ret == Object[].class
? MethodType.methodType(ret, args)
: MethodType.methodType(Object.class, args);
}
@Nullable
private byte[] generate(String className, Class<?> target, Method method, boolean unsafe) {
private byte[] generate(String className, Class<?> target, Method targetMethod, String targetDescriptor, boolean unsafe) {
var internalName = className.replace(".", "/");
// Construct a public final class which extends Object and implements MethodInstance.Delegate
var cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(V1_8, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces);
cw.visit(V17, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces);
cw.visitSource("CC generated method", null);
{ // Constructor just invokes super.
@@ -148,35 +193,26 @@ final class Generator<T> {
var mw = cw.visitMethod(ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS);
mw.visitCode();
// If we're an instance method, load the this parameter.
if (!Modifier.isStatic(method.getModifiers())) {
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
}
mw.visitLdcInsn(METHOD_CONSTANT);
// If we're an instance method, load the target as the first argument.
if (!Modifier.isStatic(targetMethod.getModifiers())) mw.visitVarInsn(ALOAD, 1);
var argIndex = 0;
for (var genericArg : method.getGenericParameterTypes()) {
var loadedArg = loadArg(mw, target, method, unsafe, genericArg, argIndex);
for (var genericArg : targetMethod.getGenericParameterTypes()) {
var loadedArg = loadArg(mw, target, targetMethod, unsafe, genericArg, argIndex);
if (loadedArg == null) return null;
if (loadedArg) argIndex++;
}
mw.visitMethodInsn(
Modifier.isStatic(method.getModifiers()) ? INVOKESTATIC : INVOKEVIRTUAL,
Type.getInternalName(method.getDeclaringClass()), method.getName(),
Type.getMethodDescriptor(method), false
);
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", targetDescriptor, false);
// We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
// we convert basic types into an immediate result.
var ret = method.getReturnType();
var ret = targetMethod.getReturnType();
if (ret != MethodResult.class) {
if (ret == void.class) {
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "()" + DESC_METHOD_RESULT, false);
} else if (ret.isPrimitive()) {
var boxed = Primitives.wrap(ret);
mw.visitMethodInsn(INVOKESTATIC, Type.getInternalName(boxed), "valueOf", "(" + Type.getDescriptor(ret) + ")" + Type.getDescriptor(boxed), false);
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
} else if (ret == Object[].class) {
mw.visitMethodInsn(INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "([Ljava/lang/Object;)" + DESC_METHOD_RESULT, false);
} else {
@@ -199,7 +235,6 @@ final class Generator<T> {
private Boolean loadArg(MethodVisitor mw, Class<?> target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex) {
if (genericArg == target) {
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
return false;
}

View File

@@ -54,7 +54,15 @@ import java.util.concurrent.locks.ReentrantLock;
@SuppressWarnings("GuardedBy") // FIXME: Hard to know what the correct thing to do is.
public final class ComputerThread {
private static final Logger LOG = LoggerFactory.getLogger(ComputerThread.class);
private static final ThreadFactory monitorFactory = ThreadUtils.factory("Computer-Monitor");
/**
* A factory for the monitor thread. We want this a slightly higher priority than normal to ensure that the computer
* thread is interrupted. This spends most of it its time idle, so should be safe.
*/
private static final ThreadFactory monitorFactory = ThreadUtils.builder("Computer-Monitor")
.setPriority((Thread.NORM_PRIORITY + Thread.MAX_PRIORITY) / 2)
.build();
private static final ThreadFactory workerFactory = ThreadUtils.lowPriorityFactory("Computer-Worker");
/**

View File

@@ -13,13 +13,13 @@ import java.util.concurrent.TimeUnit;
* <p>
* This is useful for emulators, where we'll never make any main thread calls.
*/
public class NoWorkMainThreadScheduler implements MainThreadScheduler {
public final class NoWorkMainThreadScheduler implements MainThreadScheduler {
@Override
public Executor createExecutor(MetricsObserver observer) {
return new ExecutorImpl();
}
private static class ExecutorImpl implements Executor {
private static final class ExecutorImpl implements Executor {
@Override
public boolean enqueue(Runnable task) {
throw new IllegalStateException("Cannot schedule tasks");

View File

@@ -1,3 +1,9 @@
# New features in CC: Tweaked 1.108.1
Several bug fixes:
* Prevent no-opped players breaking or placing command computers.
* Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
# New features in CC: Tweaked 1.108.0
* Remove compression from terminal/monitor packets. Vanilla applies its own compression, so this ends up being less helpful than expected.

View File

@@ -1,15 +1,7 @@
New features in CC: Tweaked 1.108.0
* Remove compression from terminal/monitor packets. Vanilla applies its own compression, so this ends up being less helpful than expected.
* `/computercraft` command now supports permission mods.
* Split some GUI textures into sprite sheets.
* Support the `%g` character class in string pattern matching.
New features in CC: Tweaked 1.108.1
Several bug fixes:
* Fix crash when playing some modded records via a disk drive.
* Fix race condition when computers attach or detach from a monitor.
* Fix the "max websocket message" config option not being read.
* `tostring` now correctly obeys `__name`.
* Fix several inconsistencies with pattern matching character classes.
* Prevent no-opped players breaking or placing command computers.
* Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
Type "help changelog" to see the full version history.

View File

@@ -11,12 +11,14 @@ import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.methods.NamedMethod;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.lang.invoke.MethodHandles;
import java.util.*;
import static dan200.computercraft.test.core.ContramapMatcher.contramap;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -116,6 +118,33 @@ public class GeneratorTest {
assertThat(methods, contains(named("withUnsafe")));
}
@Test
public void testClassNotAccessible() throws IOException, ReflectiveOperationException, LuaException {
var basicName = Basic.class.getName().replace('.', '/');
// Load our Basic class, rewriting it to be a separate (hidden) class which is not part of the same nest as
// the existing Basic.
ClassReader reader;
try (var input = getClass().getClassLoader().getResourceAsStream(basicName + ".class")) {
reader = new ClassReader(Objects.requireNonNull(input, "Cannot find " + basicName));
}
var writer = new ClassWriter(reader, 0);
reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
@Override
public void visitNestHost(String nestHost) {
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
}
}, 0);
var klass = MethodHandles.lookup().defineHiddenClass(writer.toByteArray(), true).lookupClass();
var methods = GENERATOR.getMethods(klass);
assertThat(apply(methods, klass.getConstructor().newInstance(), "go"), equalTo(MethodResult.of()));
}
public static class Basic {
@LuaFunction
public final void go() {

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Various commands for controlling computers.",
"commands.computercraft.tp.action": "Teleport to this computer",
"commands.computercraft.tp.desc": "Teleport to the location of a computer. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).",
"commands.computercraft.tp.not_player": "Cannot open terminal for non-player",
"commands.computercraft.tp.not_there": "Cannot locate computer in the world",
"commands.computercraft.tp.synopsis": "Teleport to a specific computer.",
"commands.computercraft.track.desc": "Track how long computers execute for, as well as how many events they handle. This presents information in a similar way to /forge track and can be useful for diagnosing lag.",
"commands.computercraft.track.dump.computer": "Computer",

View File

@@ -49,7 +49,7 @@ public class FabricContainerTransfer implements ContainerTransfer {
*
* @param <T> The type of the object to accept.
*/
private static class GatePredicate<T> implements Predicate<T> {
private static final class GatePredicate<T> implements Predicate<T> {
private @Nullable T instance = null;
@Override
@@ -67,7 +67,7 @@ public class FabricContainerTransfer implements ContainerTransfer {
}
}
private static class SlottedImpl extends FabricContainerTransfer implements ContainerTransfer.Slotted {
private static final class SlottedImpl extends FabricContainerTransfer implements ContainerTransfer.Slotted {
private final SlottedStorage<ItemVariant> storage;
SlottedImpl(SlottedStorage<ItemVariant> storage) {

View File

@@ -47,8 +47,6 @@
"commands.computercraft.synopsis": "Various commands for controlling computers.",
"commands.computercraft.tp.action": "Teleport to this computer",
"commands.computercraft.tp.desc": "Teleport to the location of a computer. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).",
"commands.computercraft.tp.not_player": "Cannot open terminal for non-player",
"commands.computercraft.tp.not_there": "Cannot locate computer in the world",
"commands.computercraft.tp.synopsis": "Teleport to a specific computer.",
"commands.computercraft.track.desc": "Track how long computers execute for, as well as how many events they handle. This presents information in a similar way to /forge track and can be useful for diagnosing lag.",
"commands.computercraft.track.dump.computer": "Computer",

View File

@@ -32,7 +32,7 @@ enum class Side { CLIENT, SERVER, BOTH }
)
class SideChecker : BugChecker(), BugChecker.IdentifierTreeMatcher, BugChecker.MemberSelectTreeMatcher {
override fun matchIdentifier(tree: IdentifierTree, state: VisitorState): Description {
val sym = ASTHelpers.getSymbol(tree)
val sym = ASTHelpers.getSymbol(tree)!!
return when (sym.getKind()) {
ElementKind.LOCAL_VARIABLE, ElementKind.TYPE_PARAMETER -> Description.NO_MATCH
else -> report(tree, state)

View File

@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
import { createElement as h, useContext, createContext, FunctionComponent, ReactNode } from "react";
import { createElement as h, useContext, createContext, type FunctionComponent, type ReactNode } from "react";
export type DataExport = {
readonly itemNames: Record<string, string>,

View File

@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
import { render, h, Component, Computer, PeripheralKind } from "copycat/embed";
import { render, h, Component, Computer, type PeripheralKind } from "copycat/embed";
import type { ComponentChild } from "preact";
import settingsFile from "./mount/.settings";

View File

@@ -11,7 +11,7 @@
* Yes, this would be so much nicer with next.js.
*/
import * as fs from "fs/promises";
import glob from "glob";
import { glob } from "glob";
import * as path from "path";
import { createElement, createElement as h, Fragment } from 'react';
import { renderToStaticMarkup } from "react-dom/server";
@@ -22,7 +22,7 @@ import { unified } from 'unified';
// Our components
import Recipe from "./components/Recipe.js";
import { noChildren } from "./components/support.js";
import { DataExport, WithExport } from "./components/WithExport.js";
import { type DataExport, WithExport } from "./components/WithExport.js";
(async () => {
const base = "build/illuaminate";

View File

@@ -20,7 +20,7 @@
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"importsNotUsedAsValues": "error",
"verbatimModuleSyntax": true,
"forceConsistentCasingInFileNames": true,
// Needed for some of our internal stuff.

View File

@@ -4,7 +4,6 @@
#
# SPDX-License-Identifier: MPL-2.0
"""
Upgrades textures to newer resource pack formats.