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.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import dan200.computercraft.api.lua.LuaException; import org.jetbrains.annotations.NotNull;
import dan200.computercraft.api.lua.LuaValues; import org.jetbrains.annotations.Nullable;
import dan200.computercraft.api.lua.ObjectLuaTable;
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) { public CreateLuaTable(Map<?, ?> map) {
super(map); this.map = new HashMap<>(map);
} }
public boolean getBoolean(String key) throws LuaException { public boolean getBoolean(String key) throws LuaException {
@ -86,4 +96,77 @@ public class CreateLuaTable extends ObjectLuaTable {
return Collections.unmodifiableList(tables); 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; package com.simibubi.create.compat.computercraft.peripherals;
import java.util.ArrayList; import java.util.Map;
import java.util.List;
import java.util.function.Supplier; import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import com.simibubi.create.compat.computercraft.CreateLuaTable; import com.simibubi.create.compat.computercraft.CreateLuaTable;
import com.simibubi.create.content.logistics.trains.entity.Train; 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.StationTileEntity;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket; 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.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.networking.AllPackets;
import com.simibubi.create.foundation.utility.Components; import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.StringHelper; import com.simibubi.create.foundation.utility.StringHelper;
import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import net.minecraft.nbt.ByteTag;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation; import net.minecraft.nbt.DoubleTag;
import org.jetbrains.annotations.NotNull; 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; import net.minecraftforge.network.PacketDistributor;
public class StationPeripheral extends SyncedPeripheral<StationTileEntity> { 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())); 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) @LuaFunction(mainThread = true)
public final void setSchedule(IArguments arguments) throws LuaException { public final void setSchedule(IArguments arguments) throws LuaException {
GlobalStation station = tile.getStation(); GlobalStation station = tile.getStation();
@ -137,87 +157,100 @@ public class StationPeripheral extends SyncedPeripheral<StationTileEntity> {
if (train == null) if (train == null)
throw new LuaException("there is no train present"); throw new LuaException("there is no train present");
Schedule schedule = parseSchedule(arguments); Schedule schedule = Schedule.fromTag(toCompoundTag(new CreateLuaTable(arguments.getTable(0))));
train.runtime.setSchedule(schedule, true); boolean autoSchedule = train.runtime.getSchedule() == null || train.runtime.isAutoSchedule;
train.runtime.setSchedule(schedule, autoSchedule);
} }
private static Schedule parseSchedule(IArguments arguments) throws LuaException { private static @NotNull CreateLuaTable fromCompoundTag(CompoundTag tag) throws LuaException {
CreateLuaTable scheduleTable = new CreateLuaTable(arguments.getTable(0)); return (CreateLuaTable) fromNBTTag(null, tag);
Schedule schedule = new Schedule(); }
schedule.cyclic = scheduleTable.getOptBoolean("cyclic").orElse(true); private static @NotNull Object fromNBTTag(@Nullable String key, Tag tag) throws LuaException {
CreateLuaTable entriesTable = scheduleTable.getTable("entries"); byte type = tag.getId();
for (CreateLuaTable entryTable : entriesTable.tableValues()) { if (type == Tag.TAG_BYTE && key != null && key.equals("Count"))
ScheduleEntry entry = new ScheduleEntry(); 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); for (int i = 0; i < listTag.size(); i++) {
list.put(i + 1, fromNBTTag(null, listTag.get(i)));
// 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);
}
} }
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 { private static @NotNull CompoundTag toCompoundTag(CreateLuaTable table) throws LuaException {
ResourceLocation location = new ResourceLocation(entry.getString("instruction")); return (CompoundTag) toNBTTag(null, table.getMap());
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 ScheduleWaitCondition getCondition(CreateLuaTable entry) throws LuaException { private static @NotNull Tag toNBTTag(@Nullable String key, Object value) throws LuaException {
ResourceLocation location = new ResourceLocation(entry.getString("condition")); if (value instanceof Boolean v)
return ByteTag.valueOf(v);
for (Pair<ResourceLocation, Supplier<? extends ScheduleWaitCondition>> pair : Schedule.CONDITION_TYPES) else if (value instanceof Byte || (key != null && key.equals("count")))
if (pair.getFirst().equals(location)) { return ByteTag.valueOf(((Number) value).byteValue());
ScheduleWaitCondition condition = pair.getSecond().get(); else if (value instanceof Number v) {
condition.setData(getEntryData(entry.getTable("data"))); // If number is numerical integer
if (v.intValue() == v.doubleValue())
return condition; return IntTag.valueOf(v.intValue());
}
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);
else 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 @NotNull

View file

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

View file

@ -68,7 +68,8 @@ public class FluidThresholdCondition extends CargoThresholdCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag); super.readAdditional(tag);
compareStack = ItemStack.of(tag.getCompound("Bucket")); if (tag.contains("Bucket"))
compareStack = ItemStack.of(tag.getCompound("Bucket"));
} }
@Override @Override

View file

@ -69,7 +69,8 @@ public class ItemThresholdCondition extends CargoThresholdCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { protected void readAdditional(CompoundTag tag) {
super.readAdditional(tag); super.readAdditional(tag);
stack = ItemStack.of(tag.getCompound("Item")); if (tag.contains("Item"))
stack = ItemStack.of(tag.getCompound("Item"));
} }
@Override @Override

View file

@ -107,7 +107,8 @@ public class RedstoneLinkCondition extends ScheduleWaitCondition {
@Override @Override
protected void readAdditional(CompoundTag tag) { 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 @Override

View file

@ -23,9 +23,10 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
public final CompoundTag write() { public final CompoundTag write() {
CompoundTag tag = new CompoundTag(); CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString()); tag.putString("Id", getId().toString());
tag.put("Data", data.copy()); tag.put("Data", dataCopy);
writeAdditional(tag);
return tag; return tag;
} }
@ -43,8 +44,11 @@ public abstract class ScheduleWaitCondition extends ScheduleDataEntry {
} }
ScheduleWaitCondition condition = supplier.get(); 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); condition.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
condition.readAdditional(data);
condition.data = data;
return condition; return condition;
} }

View file

@ -16,9 +16,10 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
public final CompoundTag write() { public final CompoundTag write() {
CompoundTag tag = new CompoundTag(); CompoundTag tag = new CompoundTag();
CompoundTag dataCopy = data.copy();
writeAdditional(dataCopy);
tag.putString("Id", getId().toString()); tag.putString("Id", getId().toString());
tag.put("Data", data.copy()); tag.put("Data", dataCopy);
writeAdditional(tag);
return tag; return tag;
} }
@ -36,8 +37,11 @@ public abstract class ScheduleInstruction extends ScheduleDataEntry {
} }
ScheduleInstruction scheduleDestination = supplier.get(); 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); scheduleDestination.readAdditional(tag);
CompoundTag data = tag.getCompound("Data");
scheduleDestination.readAdditional(data);
scheduleDestination.data = data;
return scheduleDestination; return scheduleDestination;
} }

View file

@ -25,4 +25,22 @@ public class StringHelper {
return builder.toString(); 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();
}
} }