Skip to content

Commit 1c677f8

Browse files
committed
Refactor, Removed cardinal Directions as Options, Updated Tests, Added Orientation Provider for mod specific behavior overrides, Consolidated Parameters, Moved Parsing to API level.
1 parent 2e8d5f7 commit 1c677f8

File tree

13 files changed

+433
-375
lines changed

13 files changed

+433
-375
lines changed

projects/common-api/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import dan200.computercraft.api.peripheral.IComputerAccess;
2020
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
2121
import dan200.computercraft.api.turtle.ITurtleAccess;
22+
import dan200.computercraft.api.turtle.TurtleOrientationProvider;
2223
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
2324
import dan200.computercraft.impl.ComputerCraftAPIService;
2425
import net.minecraft.core.BlockPos;
@@ -203,6 +204,18 @@ public static void registerRefuelHandler(TurtleRefuelHandler handler) {
203204
getInstance().registerRefuelHandler(handler);
204205
}
205206

207+
/**
208+
* Register an orientation provider for custom block placement transformations.
209+
* This allows mod authors to define how their blocks should be oriented when
210+
* turtles place them with directional parameters.
211+
*
212+
* @param provider The orientation provider to register.
213+
* @see TurtleOrientationProvider
214+
*/
215+
public static void registerOrientationProvider(TurtleOrientationProvider provider) {
216+
getInstance().registerOrientationProvider(provider);
217+
}
218+
206219
private static ComputerCraftAPIService getInstance() {
207220
return ComputerCraftAPIService.get();
208221
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
2+
//
3+
// SPDX-License-Identifier: LicenseRef-CCPL
4+
5+
package dan200.computercraft.api.turtle;
6+
7+
import net.minecraft.core.Direction;
8+
import net.minecraft.world.level.block.state.BlockState;
9+
import org.jspecify.annotations.Nullable;
10+
11+
/**
12+
* A provider for custom block orientation transformations during turtle placement.
13+
* <p>
14+
* This allows mod authors to register custom handling for their blocks when turtles
15+
* place them with orientation parameters like "left", "up", etc.
16+
*
17+
* @see dan200.computercraft.api.ComputerCraftAPI#registerOrientationProvider
18+
*/
19+
@FunctionalInterface
20+
public interface TurtleOrientationProvider {
21+
/**
22+
* Transform a block state based on turtle orientation parameters.
23+
*
24+
* @param state The original block state that was placed
25+
* @param params The orientation parameters from the turtle.place call
26+
* @return The transformed block state, or the original state if no transformation is needed.
27+
* Return null to indicate this provider doesn't handle this block.
28+
*/
29+
@Nullable
30+
BlockState transform(BlockState state, OrientationParameters params);
31+
32+
/**
33+
* Parameters passed to turtle.place for controlling block orientation.
34+
*/
35+
public static final class OrientationParameters {
36+
private final @Nullable Direction blockFacing;
37+
private final @Nullable Boolean isTop;
38+
private final boolean isUpsideDown;
39+
private final boolean isGroundAttachment;
40+
private final @Nullable Direction clickFace;
41+
42+
public OrientationParameters(@Nullable Direction blockFacing,
43+
@Nullable Boolean isTop,
44+
boolean isUpsideDown,
45+
boolean isGroundAttachment,
46+
@Nullable Direction clickFace) {
47+
this.blockFacing = blockFacing;
48+
this.isTop = isTop;
49+
this.isUpsideDown = isUpsideDown;
50+
this.isGroundAttachment = isGroundAttachment;
51+
this.clickFace = clickFace;
52+
}
53+
54+
/**
55+
* The direction the block should face, or null if no facing was specified.
56+
*/
57+
public @Nullable Direction getBlockFacing() {
58+
return blockFacing;
59+
}
60+
61+
/**
62+
* Whether the block should be on top (true), bottom (false), or unspecified (null).
63+
* Primarily used for slabs.
64+
*/
65+
public @Nullable Boolean getIsTop() {
66+
return isTop;
67+
}
68+
69+
/**
70+
* Whether the block should be placed upside down (for stairs, etc.).
71+
*/
72+
public boolean isUpsideDown() {
73+
return isUpsideDown;
74+
}
75+
76+
/**
77+
* Whether this is a ground attachment (for torches, etc.).
78+
*/
79+
public boolean isGroundAttachment() {
80+
return isGroundAttachment;
81+
}
82+
83+
/**
84+
* Which face of the target block to click, or null if not specified.
85+
*/
86+
public @Nullable Direction getClickFace() {
87+
return clickFace;
88+
}
89+
}
90+
}

projects/common-api/src/main/java/dan200/computercraft/impl/ComputerCraftAPIService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import dan200.computercraft.api.network.wired.WiredNode;
1919
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
2020
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
21+
import dan200.computercraft.api.turtle.TurtleOrientationProvider;
2122
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
2223
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
2324
import net.minecraft.core.BlockPos;
@@ -67,6 +68,8 @@ static ComputerCraftAPIService get() {
6768

6869
void registerRefuelHandler(TurtleRefuelHandler handler);
6970

71+
void registerOrientationProvider(TurtleOrientationProvider provider);
72+
7073
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
7174

7275
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();

projects/common/src/main/java/dan200/computercraft/impl/AbstractComputerCraftAPI.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import dan200.computercraft.api.network.wired.WiredNode;
1818
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
1919
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
20+
import dan200.computercraft.api.turtle.TurtleOrientationProvider;
2021
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
2122
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
2223
import dan200.computercraft.core.filesystem.WritableFileMount;
@@ -112,6 +113,11 @@ public final void registerRefuelHandler(TurtleRefuelHandler handler) {
112113
TurtleRefuelHandlers.register(handler);
113114
}
114115

116+
@Override
117+
public final void registerOrientationProvider(TurtleOrientationProvider provider) {
118+
TurtleOrientationProviders.register(provider);
119+
}
120+
115121
@Override
116122
public final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId() {
117123
return turtleUpgradeRegistryId;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
package dan200.computercraft.impl;
6+
7+
import dan200.computercraft.api.turtle.TurtleOrientationProvider;
8+
import net.minecraft.world.level.block.state.BlockState;
9+
import org.jspecify.annotations.Nullable;
10+
11+
import java.util.List;
12+
import java.util.Objects;
13+
import java.util.concurrent.CopyOnWriteArrayList;
14+
15+
/**
16+
* Registry of {@link TurtleOrientationProvider}s.
17+
*/
18+
public final class TurtleOrientationProviders {
19+
private static final List<TurtleOrientationProvider> providers = new CopyOnWriteArrayList<>();
20+
21+
private TurtleOrientationProviders() {
22+
}
23+
24+
public static synchronized void register(TurtleOrientationProvider provider) {
25+
Objects.requireNonNull(provider, "provider cannot be null");
26+
providers.add(provider);
27+
}
28+
29+
@Nullable
30+
public static BlockState transform(BlockState state, TurtleOrientationProvider.OrientationParameters params) {
31+
for (var provider : providers) {
32+
var result = provider.transform(state, params);
33+
if (result != null) {
34+
return result;
35+
}
36+
}
37+
return null;
38+
}
39+
}

projects/common/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import dan200.computercraft.core.metrics.MetricsObserver;
1414
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
1515
import dan200.computercraft.shared.turtle.core.*;
16+
import net.minecraft.core.Direction;
1617
import org.jspecify.annotations.Nullable;
1718

19+
import java.util.Locale;
1820
import java.util.Map;
1921
import java.util.Optional;
2022

@@ -221,15 +223,19 @@ public final MethodResult digDown(Optional<TurtleSide> side) {
221223
* @param args Arguments to place.
222224
* @return The turtle command result.
223225
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
224-
* @cc.tparam [opt] string ... Multiple orientation parameters: "north", "south", "east", "west" for absolute direction, "left", "right", "back" for relative direction, "up"/"down" for face clicking, "face_up"/"face_down" for vertical facing, "top"/"bottom" for slabs, "upside_down" for stairs, "ground" for torches on floor. Can combine multiple parameters.
226+
* @cc.tparam [opt] table options Table with placement options: { facing = "left", position = "top", text = "Hello" }.
227+
* The "facing" field can be a string of a relative direction left, right, up, down, forward, backward.
228+
* The "position" field sets slab/stair positioning/upside down with top ("upsidedown"), bottom as well as ground for torch like items that shouldnt be placed on a wall.
229+
* The "text" field sets sign text.
230+
* if you just pass a string it is passed like it normally would be to a sign.
225231
* @cc.treturn boolean Whether the block could be placed.
226232
* @cc.treturn string|nil The reason the block was not placed.
227233
* @cc.since 1.4
228-
* @cc.changed 1.116.1 Added optional orientation parameters for controlling block rotation and positioning. Multiple parameters can be combined.
234+
* @cc.changed 1.116.1 Added table-based placement options for cleaner API usage.
229235
*/
230236
@LuaFunction
231237
public final MethodResult place(IArguments args) throws LuaException {
232-
return trackCommand(new TurtlePlaceCommand(InteractDirection.FORWARD, args.getAll()));
238+
return trackCommand(new TurtlePlaceCommand(InteractDirection.FORWARD, parseArgumentsToParameters(args)));
233239
}
234240

235241
/**
@@ -245,7 +251,7 @@ public final MethodResult place(IArguments args) throws LuaException {
245251
*/
246252
@LuaFunction
247253
public final MethodResult placeUp(IArguments args) throws LuaException {
248-
return trackCommand(new TurtlePlaceCommand(InteractDirection.UP, args.getAll()));
254+
return trackCommand(new TurtlePlaceCommand(InteractDirection.UP, parseArgumentsToParameters(args)));
249255
}
250256

251257
/**
@@ -261,7 +267,7 @@ public final MethodResult placeUp(IArguments args) throws LuaException {
261267
*/
262268
@LuaFunction
263269
public final MethodResult placeDown(IArguments args) throws LuaException {
264-
return trackCommand(new TurtlePlaceCommand(InteractDirection.DOWN, args.getAll()));
270+
return trackCommand(new TurtlePlaceCommand(InteractDirection.DOWN, parseArgumentsToParameters(args)));
265271
}
266272

267273
/**
@@ -826,4 +832,115 @@ private static int checkCount(Optional<Integer> countArg) throws LuaException {
826832
if (count < 0 || count > 64) throw new LuaException("Item count " + count + " out of range");
827833
return count;
828834
}
835+
836+
/**
837+
* Parse table and string based arguments for turtle.place
838+
* returning structured PlacementParameters.
839+
*/
840+
private TurtlePlaceCommand.PlacementParameters parseArgumentsToParameters(IArguments args) throws LuaException {
841+
if (args.count() == 0) {
842+
return new TurtlePlaceCommand.PlacementParameters(null, null, false, false, null, null);
843+
}
844+
845+
// Check if first argument is a table
846+
var firstArg = args.get(0);
847+
if (firstArg instanceof Map<?, ?> table) {
848+
return parseTableToParameters(table);
849+
}
850+
851+
// Default sign handling
852+
return new TurtlePlaceCommand.PlacementParameters(null, null, false, false, null, args.getString(0));
853+
}
854+
855+
private TurtlePlaceCommand.PlacementParameters parseTableToParameters(Map<?, ?> table) throws LuaException {
856+
Direction blockFacing = null;
857+
Boolean isTop = null;
858+
boolean isUpsideDown = false;
859+
boolean isGroundAttachment = false;
860+
Direction clickFace = null;
861+
String signText = null;
862+
863+
// Handle "text" field for signs
864+
if (table.containsKey("text")) {
865+
var text = table.get("text");
866+
if (text instanceof String string) {
867+
signText = string;
868+
} else {
869+
throw new LuaException("Expected string value for 'text' field");
870+
}
871+
}
872+
873+
// Handle "facing" field
874+
if (table.containsKey("facing")) {
875+
var facing = table.get("facing");
876+
if (facing instanceof String string) {
877+
var parsed = parseDirectionParameter(string);
878+
blockFacing = parsed.blockFacing;
879+
if (parsed.isUpsideDown) isUpsideDown = true;
880+
if (parsed.clickFace != null) clickFace = parsed.clickFace;
881+
if (parsed.isGroundAttachment) isGroundAttachment = true;
882+
} else {
883+
throw new LuaException("Expected string value for 'facing' field");
884+
}
885+
}
886+
887+
// Handle "position" field for slabs/stairs
888+
if (table.containsKey("position")) {
889+
var position = table.get("position");
890+
if (position instanceof String posStr) {
891+
switch (posStr.toLowerCase(Locale.ROOT)) {
892+
// top is reused here, it can set both itTop and isUpsideDown since blocks that would use these should'nt be affected by ones it doesnt use.
893+
case "top" -> {
894+
isTop = true;
895+
isUpsideDown = true;
896+
}
897+
// included for completeness but this is the default behaviour, remove?
898+
case "bottom" -> isTop = false;
899+
//torch like item specific so that it isnt placed against a wall.
900+
case "ground" -> isGroundAttachment = true;
901+
default -> throw new LuaException("Unknown position value: " + posStr);
902+
}
903+
} else {
904+
throw new LuaException("Expected string value for 'position' field");
905+
}
906+
}
907+
908+
return new TurtlePlaceCommand.PlacementParameters(blockFacing, isTop, isUpsideDown, isGroundAttachment, clickFace, signText);
909+
}
910+
911+
private record ParsedDirection(
912+
@Nullable Direction blockFacing,
913+
@Nullable Boolean isTop,
914+
boolean isUpsideDown,
915+
boolean isGroundAttachment,
916+
@Nullable Direction clickFace
917+
) {}
918+
919+
private ParsedDirection parseDirectionParameter(String orientation) {
920+
Direction blockFacing = null;
921+
Boolean isTop = null;
922+
boolean isUpsideDown = false;
923+
boolean isGroundAttachment = false;
924+
Direction clickFace = null;
925+
926+
switch (orientation.toLowerCase(Locale.ROOT)) {
927+
case "up" -> {
928+
clickFace = Direction.DOWN;
929+
blockFacing = Direction.UP;
930+
}
931+
case "down" -> {
932+
clickFace = Direction.UP;
933+
blockFacing = Direction.DOWN;
934+
}
935+
case "forward" -> blockFacing = turtle.getDirection();
936+
case "left" -> blockFacing = turtle.getDirection().getCounterClockWise();
937+
case "right" -> blockFacing = turtle.getDirection().getClockWise();
938+
case "back", "backward" -> blockFacing = turtle.getDirection().getOpposite();
939+
case "top" -> isTop = true;
940+
case "bottom" -> isTop = false;
941+
case "ground" -> isGroundAttachment = true;
942+
}
943+
944+
return new ParsedDirection(blockFacing, isTop, isUpsideDown, isGroundAttachment, clickFace);
945+
}
829946
}

0 commit comments

Comments
 (0)