Added getSchedule to train station lua API

- Added getSchedule which serializes the currently present train's schedule into a lua table
- Refactored StationPeripheral#setSchedule to use a more generic method of serializing NBT tags to lua tables
- Moved schedule entry special data from root tag to "Data"
- Added StringHelper#camelCaseToSnakeCase
- Added variety of put methods to CreateLuaTable
This commit is contained in:
caelwarner 2023-03-11 11:15:58 -08:00
parent 31ad3aa671
commit 909484ed5b
No known key found for this signature in database
GPG key ID: 514BEF5EADE889FF
9 changed files with 242 additions and 96 deletions

View file

@ -3,20 +3,30 @@ package com.simibubi.create.compat.computercraft;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.api.lua.ObjectLuaTable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CreateLuaTable extends ObjectLuaTable {
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaTable;
import dan200.computercraft.api.lua.LuaValues;
public class CreateLuaTable implements LuaTable<Object, Object> {
private final Map<Object, Object> map;
public CreateLuaTable() {
this.map = new HashMap<>();
}
public CreateLuaTable(Map<?, ?> map) {
super(map);
this.map = new HashMap<>(map);
}
public boolean getBoolean(String key) throws LuaException {
@ -86,4 +96,77 @@ public class CreateLuaTable extends ObjectLuaTable {
return Collections.unmodifiableList(tables);
}
public Map<Object, Object> getMap() {
return map;
}
@Nullable
@Override
public Object put(Object key, Object value) {
return map.put(key, value);
}
public void putBoolean(String key, boolean value) {
map.put(key, value);
}
public void putDouble(String key, double value) {
map.put(key, value);
}
public void putString(String key, String value) {
map.put(key, value);
}
public void putTable(String key, CreateLuaTable value) {
map.put(key, value);
}
public void putTable(int i, CreateLuaTable value) {
map.put(i, value);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return map.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return map.containsValue(o);
}
@Override
public Object get(Object o) {
return map.get(o);
}
@NotNull
@Override
public Set<Object> keySet() {
return map.keySet();
}
@NotNull
@Override
public Collection<Object> values() {
return map.values();
}
@NotNull
@Override
public Set<Entry<Object, Object>> entrySet() {
return map.entrySet();
}
}

View file

@ -1,8 +1,10 @@
package com.simibubi.create.compat.computercraft.peripherals;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.Map;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.compat.computercraft.CreateLuaTable;
import com.simibubi.create.content.logistics.trains.entity.Train;
@ -10,20 +12,21 @@ import com.simibubi.create.content.logistics.trains.management.edgePoint.station
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket;
import com.simibubi.create.content.logistics.trains.management.schedule.Schedule;
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleEntry;
import com.simibubi.create.content.logistics.trains.management.schedule.condition.ScheduleWaitCondition;
import com.simibubi.create.content.logistics.trains.management.schedule.destination.ScheduleInstruction;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.StringHelper;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import net.minecraft.nbt.ByteTag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import net.minecraft.nbt.DoubleTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraftforge.network.PacketDistributor;
public class StationPeripheral extends SyncedPeripheral<StationTileEntity> {
@ -127,6 +130,23 @@ public class StationPeripheral extends SyncedPeripheral<StationTileEntity> {
AllPackets.channel.send(PacketDistributor.ALL.noArg(), new TrainEditPacket.TrainEditReturnPacket(train.id, name, train.icon.getId()));
}
@LuaFunction
public final CreateLuaTable getSchedule() throws LuaException {
GlobalStation station = tile.getStation();
if (station == null)
throw new LuaException("train station does not exist");
Train train = station.getPresentTrain();
if (train == null)
throw new LuaException("there is no train present");
Schedule schedule = train.runtime.getSchedule();
if (schedule == null)
throw new LuaException("train doesn't have a schedule");
return fromCompoundTag(schedule.write());
}
@LuaFunction(mainThread = true)
public final void setSchedule(IArguments arguments) throws LuaException {
GlobalStation station = tile.getStation();
@ -137,87 +157,100 @@ public class StationPeripheral extends SyncedPeripheral<StationTileEntity> {
if (train == null)
throw new LuaException("there is no train present");
Schedule schedule = parseSchedule(arguments);
train.runtime.setSchedule(schedule, true);
Schedule schedule = Schedule.fromTag(toCompoundTag(new CreateLuaTable(arguments.getTable(0))));
boolean autoSchedule = train.runtime.getSchedule() == null || train.runtime.isAutoSchedule;
train.runtime.setSchedule(schedule, autoSchedule);
}
private static Schedule parseSchedule(IArguments arguments) throws LuaException {
CreateLuaTable scheduleTable = new CreateLuaTable(arguments.getTable(0));
Schedule schedule = new Schedule();
private static @NotNull CreateLuaTable fromCompoundTag(CompoundTag tag) throws LuaException {
return (CreateLuaTable) fromNBTTag(null, tag);
}
schedule.cyclic = scheduleTable.getOptBoolean("cyclic").orElse(true);
CreateLuaTable entriesTable = scheduleTable.getTable("entries");
private static @NotNull Object fromNBTTag(@Nullable String key, Tag tag) throws LuaException {
byte type = tag.getId();
for (CreateLuaTable entryTable : entriesTable.tableValues()) {
ScheduleEntry entry = new ScheduleEntry();
if (type == Tag.TAG_BYTE && key != null && key.equals("Count"))
return ((NumericTag) tag).getAsByte();
else if (type == Tag.TAG_BYTE)
return ((NumericTag) tag).getAsByte() != 0;
else if (type == Tag.TAG_INT || type == Tag.TAG_LONG)
return ((NumericTag) tag).getAsLong();
else if (type == Tag.TAG_FLOAT || type == Tag.TAG_DOUBLE)
return ((NumericTag) tag).getAsDouble();
else if (type == Tag.TAG_STRING)
return tag.getAsString();
else if (type == Tag.TAG_LIST) {
CreateLuaTable list = new CreateLuaTable();
ListTag listTag = (ListTag) tag;
entry.instruction = getInstruction(entryTable);
// Add conditions
if (entry.instruction.supportsConditions()) {
for (CreateLuaTable conditionsListTable : entryTable.getTable("conditions").tableValues()) {
List<ScheduleWaitCondition> conditionsList = new ArrayList<>();
for (CreateLuaTable conditionTable : conditionsListTable.tableValues()) {
conditionsList.add(getCondition(conditionTable));
}
entry.conditions.add(conditionsList);
}
for (int i = 0; i < listTag.size(); i++) {
list.put(i + 1, fromNBTTag(null, listTag.get(i)));
}
schedule.entries.add(entry);
return list;
} else if (type == Tag.TAG_COMPOUND) {
CreateLuaTable table = new CreateLuaTable();
CompoundTag compoundTag = (CompoundTag) tag;
for (String compoundKey : compoundTag.getAllKeys()) {
table.put(
StringHelper.camelCaseToSnakeCase(compoundKey),
fromNBTTag(compoundKey, compoundTag.get(compoundKey))
);
}
return table;
}
return schedule;
throw new LuaException("unknown tag type " + tag.getType().getName());
}
private static ScheduleInstruction getInstruction(CreateLuaTable entry) throws LuaException {
ResourceLocation location = new ResourceLocation(entry.getString("instruction"));
for (Pair<ResourceLocation, Supplier<? extends ScheduleInstruction>> pair : Schedule.INSTRUCTION_TYPES)
if (pair.getFirst().equals(location)) {
ScheduleInstruction instruction = pair.getSecond().get();
instruction.setData(getEntryData(entry.getTable("data")));
return instruction;
}
throw new LuaException("instruction " + location + " is not a valid instruction type");
private static @NotNull CompoundTag toCompoundTag(CreateLuaTable table) throws LuaException {
return (CompoundTag) toNBTTag(null, table.getMap());
}
private static ScheduleWaitCondition getCondition(CreateLuaTable entry) throws LuaException {
ResourceLocation location = new ResourceLocation(entry.getString("condition"));
for (Pair<ResourceLocation, Supplier<? extends ScheduleWaitCondition>> pair : Schedule.CONDITION_TYPES)
if (pair.getFirst().equals(location)) {
ScheduleWaitCondition condition = pair.getSecond().get();
condition.setData(getEntryData(entry.getTable("data")));
return condition;
}
throw new LuaException("condition " + location + " is not a valid condition type");
}
private static CompoundTag getEntryData(CreateLuaTable data) throws LuaException {
CompoundTag tag = new CompoundTag();
for (String key : data.stringKeySet()) {
String tagKey = StringHelper.snakeCaseToCamelCase(key);
Object value = data.get(key);
if (value instanceof Boolean)
tag.putBoolean(tagKey, (Boolean) value);
else if (value instanceof Number)
tag.putDouble(tagKey, ((Number) value).doubleValue());
else if (value instanceof String)
tag.putString(tagKey, (String) value);
private static @NotNull Tag toNBTTag(@Nullable String key, Object value) throws LuaException {
if (value instanceof Boolean v)
return ByteTag.valueOf(v);
else if (value instanceof Byte || (key != null && key.equals("count")))
return ByteTag.valueOf(((Number) value).byteValue());
else if (value instanceof Number v) {
// If number is numerical integer
if (v.intValue() == v.doubleValue())
return IntTag.valueOf(v.intValue());
else
throw new LuaException("");
return DoubleTag.valueOf(v.doubleValue());
} else if (value instanceof String v)
return StringTag.valueOf(v);
else if (value instanceof Map<?, ?> v && v.containsKey(1.0)) { // List
ListTag list = new ListTag();
for (Object o : v.values()) {
list.add(toNBTTag(null, o));
}
return list;
} else if (value instanceof Map<?, ?> v) { // Table/Map
CompoundTag compound = new CompoundTag();
for (Object objectKey : v.keySet()) {
if (!(objectKey instanceof String compoundKey))
throw new LuaException("table key is not of type string");
compound.put(
// Items serialize their resource location as "id" and not as "Id".
// This check is needed to see if the 'i' should be left lowercase or not.
// Items store "count" in the same compound tag, so we can check for its presence to see if this is a serialized item
compoundKey.equals("id") && v.containsKey("count") ? "id" : StringHelper.snakeCaseToCamelCase(compoundKey),
toNBTTag(compoundKey, v.get(compoundKey))
);
}
return compound;
}
return tag;
throw new LuaException("unknown object type " + value.getClass().getName());
}
@NotNull

View file

@ -18,6 +18,7 @@ public abstract class ScheduleDataEntry implements IScheduleInput {
@Override
public void setData(CompoundTag data) {
this.data = data;
readAdditional(data);
}
protected void writeAdditional(CompoundTag tag) {};

View file

@ -68,7 +68,8 @@ public class FluidThresholdCondition extends CargoThresholdCondition {
@Override
protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag);
compareStack = ItemStack.of(tag.getCompound("Bucket"));
if (tag.contains("Bucket"))
compareStack = ItemStack.of(tag.getCompound("Bucket"));
}
@Override
@ -139,4 +140,4 @@ public class FluidThresholdCondition extends CargoThresholdCondition {
Math.max(0, getThreshold() + offset), Lang.translateDirect("schedule.condition.threshold.buckets"));
}
}
}

View file

@ -69,7 +69,8 @@ public class ItemThresholdCondition extends CargoThresholdCondition {
@Override
protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag);
stack = ItemStack.of(tag.getCompound("Item"));
if (tag.contains("Item"))
stack = ItemStack.of(tag.getCompound("Item"));
}
@Override
@ -131,4 +132,4 @@ public class ItemThresholdCondition extends CargoThresholdCondition {
Math.max(0, getThreshold() + offset),
Lang.translateDirect("schedule.condition.threshold." + (inStacks() ? "stacks" : "items")));
}
}
}

View file

@ -107,7 +107,8 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition {
@Override
protected void readAdditional(CompoundTag tag) {
freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c)));
if (tag.contains("Frequency"))
freq = Couple.deserializeEach(tag.getList("Frequency", Tag.TAG_COMPOUND), c -> Frequency.of(ItemStack.of(c)));
}
@Override
@ -118,7 +119,7 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition {
.titled(Lang.translateDirect("schedule.condition.redstone_link.frequency_state")),
"Inverted");
}
@Override
public MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag) {
return Lang.translateDirect("schedule.condition.redstone_link.status");

View file

@ -16,16 +16,17 @@ import net.minecraft.world.level.Level;
public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
public abstract boolean tickCompletion(Level level, Train train, CompoundTag context);
protected void requestStatusToUpdate(CompoundTag context) {
context.putInt("StatusVersion", context.getInt("StatusVersion") + 1);
}
public final CompoundTag write() {
CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString());
tag.put("Data", data.copy());
writeAdditional(tag);
tag.put("Data", dataCopy);
return tag;
}
@ -43,11 +44,14 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
}
ScheduleWaitCondition condition = supplier.get();
condition.data = tag.getCompound("Data");
// Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag
condition.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
condition.readAdditional(data);
condition.data = data;
return condition;
}
public abstract MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag);
}
}

View file

@ -16,9 +16,10 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
public final CompoundTag write() {
CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString());
tag.put("Data", data.copy());
writeAdditional(tag);
tag.put("Data", dataCopy);
return tag;
}
@ -36,9 +37,12 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
}
ScheduleInstruction scheduleDestination = supplier.get();
scheduleDestination.data = tag.getCompound("Data");
// Left around for migration purposes. Data added in writeAdditional has moved into the "Data" tag
scheduleDestination.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
scheduleDestination.readAdditional(data);
scheduleDestination.data = data;
return scheduleDestination;
}
}
}

View file

@ -25,4 +25,22 @@ public class StringHelper {
return builder.toString();
}
public static String camelCaseToSnakeCase(String text) {
StringBuilder builder = new StringBuilder();
for (char c : text.toCharArray()) {
if (Character.isUpperCase(c)) {
builder.append('_');
builder.append(Character.toLowerCase(c));
} else {
builder.append(c);
}
}
if (builder.length() > 0 && builder.charAt(0) == '_')
builder.deleteCharAt(0);
return builder.toString();
}
}