diff --git a/src/main/java/com/simibubi/create/content/trains/schedule/Schedule.java b/src/main/java/com/simibubi/create/content/trains/schedule/Schedule.java index 910e70829b..45e4cafab3 100644 --- a/src/main/java/com/simibubi/create/content/trains/schedule/Schedule.java +++ b/src/main/java/com/simibubi/create/content/trains/schedule/Schedule.java @@ -5,9 +5,11 @@ import java.util.function.Supplier; import com.simibubi.create.Create; +import com.simibubi.create.content.trains.schedule.condition.DeliverPackageCondition; import com.simibubi.create.content.trains.schedule.condition.FluidThresholdCondition; import com.simibubi.create.content.trains.schedule.condition.IdleCargoCondition; import com.simibubi.create.content.trains.schedule.condition.ItemThresholdCondition; +import com.simibubi.create.content.trains.schedule.condition.RetrievePackageCondition; import com.simibubi.create.content.trains.schedule.condition.PlayerPassengerCondition; import com.simibubi.create.content.trains.schedule.condition.RedstoneLinkCondition; import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition; @@ -63,6 +65,8 @@ public class Schedule { registerCondition("idle", IdleCargoCondition::new); registerCondition("unloaded", StationUnloadedCondition::new); registerCondition("powered", StationPoweredCondition::new); + registerCondition("retrieve_package", RetrievePackageCondition::new); + registerCondition("deliver_package", DeliverPackageCondition::new); } private static void registerInstruction(String name, Supplier factory) { diff --git a/src/main/java/com/simibubi/create/content/trains/schedule/condition/DeliverPackageCondition.java b/src/main/java/com/simibubi/create/content/trains/schedule/condition/DeliverPackageCondition.java new file mode 100644 index 0000000000..50041bc310 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/trains/schedule/condition/DeliverPackageCondition.java @@ -0,0 +1,69 @@ +package com.simibubi.create.content.trains.schedule.condition; + +import com.google.common.collect.ImmutableList; +import com.simibubi.create.Create; +import com.simibubi.create.content.logistics.box.PackageItem; +import com.simibubi.create.content.logistics.box.PackageStyles; +import com.simibubi.create.content.logistics.packagePort.postbox.PostboxBlockEntity; +import com.simibubi.create.content.trains.entity.Carriage; +import com.simibubi.create.content.trains.entity.CarriageContraptionEntity; +import com.simibubi.create.content.trains.entity.Train; +import com.simibubi.create.content.trains.station.GlobalStation.GlobalPackagePort; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; +import com.simibubi.create.foundation.utility.CreateLang; + +import net.createmod.catnip.data.Pair; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.IItemHandlerModifiable; +import net.neoforged.neoforge.items.ItemHandlerHelper; + +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map.Entry; + +public class DeliverPackageCondition extends ScheduleWaitCondition { + + public DeliverPackageCondition() { + } + + @Override + public boolean tickCompletion(Level level, Train train, CompoundTag context) { + return true; + } + + @Override + public MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag) { + return CreateLang.translateDirect("schedule.condition.deliver_package.status"); + } + + @Override + public Pair getSummary() { + return Pair.of(PackageStyles.getDefaultBox(), CreateLang.translateDirect("schedule.condition.deliver_package.summary")); + } + + @Override + public List getTitleAs(String type) { + return ImmutableList.of( + Component.translatable(getId().getNamespace() + ".schedule." + type + "." + getId().getPath())); + } + + @Override + public ResourceLocation getId() { + return Create.asResource("deliver_package"); + } + +} diff --git a/src/main/java/com/simibubi/create/content/trains/schedule/condition/RetrievePackageCondition.java b/src/main/java/com/simibubi/create/content/trains/schedule/condition/RetrievePackageCondition.java new file mode 100644 index 0000000000..8f35bc3cad --- /dev/null +++ b/src/main/java/com/simibubi/create/content/trains/schedule/condition/RetrievePackageCondition.java @@ -0,0 +1,88 @@ +package com.simibubi.create.content.trains.schedule.condition; + +import com.google.common.collect.ImmutableList; +import com.simibubi.create.Create; +import com.simibubi.create.content.logistics.box.PackageItem; +import com.simibubi.create.content.logistics.box.PackageStyles; +import com.simibubi.create.content.logistics.packagePort.postbox.PostboxBlockEntity; +import com.simibubi.create.content.trains.entity.Carriage; +import com.simibubi.create.content.trains.entity.CarriageContraptionEntity; +import com.simibubi.create.content.trains.entity.Train; + +import com.simibubi.create.content.trains.station.GlobalStation.GlobalPackagePort; +import com.simibubi.create.foundation.gui.ModularGuiLineBuilder; +import com.simibubi.create.foundation.utility.CreateLang; + +import net.createmod.catnip.data.Pair; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; + +import net.neoforged.neoforge.items.IItemHandlerModifiable; + +import net.neoforged.neoforge.items.ItemHandlerHelper; + +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map.Entry; + +public class RetrievePackageCondition extends ScheduleWaitCondition { + + public RetrievePackageCondition() { + data.putString("Filter", ""); + } + + @Override + public boolean tickCompletion(Level level, Train train, CompoundTag context) { + return true; + } + + @Override + public MutableComponent getWaitingStatus(Level level, Train train, CompoundTag tag) { + return CreateLang.translateDirect("schedule.condition.retrieve_package.status", getFilter()); + } + + @Override + public Pair getSummary() { + return Pair.of(PackageStyles.getDefaultBox(), CreateLang.translateDirect("schedule.condition.retrieve_package.summary")); + } + + @Override + public List getTitleAs(String type) { + return ImmutableList.of( + Component.translatable(getId().getNamespace() + ".schedule." + type + "." + getId().getPath()), + CreateLang.translateDirect("schedule.condition.retrieve_package.addressed", getFilter()) + .withStyle(ChatFormatting.DARK_AQUA)); + } + + @Override + public ResourceLocation getId() { + return Create.asResource("retrieve_package"); + } + + @OnlyIn(Dist.CLIENT) + protected void modifyEditBox(EditBox box) { + box.setFilter(s -> StringUtils.countMatches(s, '*') <= 3); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void initConfigurationWidgets(ModularGuiLineBuilder builder) { + builder.addTextInput(0, 121, (e, t) -> modifyEditBox(e), "Filter"); + } + + public String getFilter() { + return textData("Filter"); + } +} diff --git a/src/main/java/com/simibubi/create/content/trains/station/GlobalStation.java b/src/main/java/com/simibubi/create/content/trains/station/GlobalStation.java index adf1ffd470..dd58276d2f 100644 --- a/src/main/java/com/simibubi/create/content/trains/station/GlobalStation.java +++ b/src/main/java/com/simibubi/create/content/trains/station/GlobalStation.java @@ -1,7 +1,9 @@ package com.simibubi.create.content.trains.station; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -15,6 +17,13 @@ import com.simibubi.create.content.trains.entity.Train; import com.simibubi.create.content.trains.graph.DimensionPalette; import com.simibubi.create.content.trains.graph.TrackNode; +import com.simibubi.create.content.trains.schedule.ScheduleEntry; +import com.simibubi.create.content.trains.schedule.condition.DeliverPackageCondition; +import com.simibubi.create.content.trains.schedule.condition.RetrievePackageCondition; +import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition; +import com.simibubi.create.content.trains.schedule.destination.DeliverPackagesInstruction; +import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction; +import com.simibubi.create.content.trains.schedule.destination.FetchPackagesInstruction; import com.simibubi.create.content.trains.signal.SingleBlockEntityEdgePoint; import net.createmod.catnip.nbt.NBTHelper; @@ -170,12 +179,170 @@ public static class GlobalPackagePort { public boolean primed = false; } + private List getRetrievePackageFilters() { + Train train = getNearestTrain(); + if (train == null || train.runtime.schedule == null || train.runtime.schedule.entries.isEmpty()) new ArrayList(); + ScheduleEntry entry = train.runtime.schedule.entries.get(train.runtime.currentEntry); + List filters = new ArrayList<>(); + + if (entry.instruction instanceof FetchPackagesInstruction fetchPackagesInstruction) { + filters.add(fetchPackagesInstruction.getFilter()); + return filters; + } + + for (var i = 0; i < entry.conditions.size(); i++) { + List entryConditions = entry.conditions.get(i); + for (var entryCondition : entryConditions) { + if (entryCondition instanceof RetrievePackageCondition retrieveCondition) { + filters.add(retrieveCondition.getFilter()); + } + } + } + return filters; + } + + private boolean hasDeliverPackageCondition() { + Train train = getNearestTrain(); + if (train == null || train.runtime.schedule == null || train.runtime.schedule.entries.isEmpty()) return false; + ScheduleEntry entry = train.runtime.schedule.entries.get(train.runtime.currentEntry); + for (var i = 0; i < entry.conditions.size(); i++) { + List entryConditions = entry.conditions.get(i); + for (var entryCondition : entryConditions) { + if (entryCondition instanceof DeliverPackageCondition) { + return true; + } + } + } + return false; + } + + private boolean isDeliverPackageInstruction() { + Train train = getNearestTrain(); + if (train == null || train.runtime.schedule == null || train.runtime.schedule.entries.isEmpty()) return false; + ScheduleEntry entry = train.runtime.schedule.entries.get(train.runtime.currentEntry); + if (entry.instruction instanceof DeliverPackagesInstruction) { + return true; + } + return false; + } + + private boolean shouldDeliverPackages() { + return hasDeliverPackageCondition() || isDeliverPackageInstruction(); + } + + public void runMailTransferScheduled() { + Train train = getPresentTrain(); + if (train == null || connectedPorts.isEmpty()) + return; + Level level = null; + + if (train.runtime.schedule == null) return; + + + var shouldDeliverPackages = shouldDeliverPackages(); + var retrievePackageFilters = getRetrievePackageFilters(); + for (Carriage carriage : train.carriages) { + if (level == null) { + CarriageContraptionEntity entity = carriage.anyAvailableEntity(); + if (entity != null && entity.level() instanceof ServerLevel sl) + level = sl.getServer() + .getLevel(getBlockEntityDimension()); + } + + IItemHandlerModifiable carriageInventory = carriage.storage.getAllItems(); + if (carriageInventory == null) + continue; + + // Import from station + if (retrievePackageFilters != null && !retrievePackageFilters.isEmpty()) { + for (Entry entry : connectedPorts.entrySet()) { + GlobalPackagePort port = entry.getValue(); + BlockPos pos = entry.getKey(); + PostboxBlockEntity box = null; + + IItemHandlerModifiable postboxInventory = port.offlineBuffer; + if (level != null && level.isLoaded(pos) + && level.getBlockEntity(pos) instanceof PostboxBlockEntity ppbe) { + postboxInventory = ppbe.inventory; + box = ppbe; + } + + for (int slot = 0; slot < postboxInventory.getSlots(); slot++) { + ItemStack stack = postboxInventory.getStackInSlot(slot); + if (!PackageItem.isPackage(stack)) + continue; + if (PackageItem.matchAddress(stack, port.address)) + continue; + + // Only import if we have matching conditions + for (var filter : retrievePackageFilters) { + if (PackageItem.matchAddress(stack, filter)) { + ItemStack result = ItemHandlerHelper.insertItemStacked(carriageInventory, stack, false); + if (!result.isEmpty()) + break; + + postboxInventory.setStackInSlot(slot, ItemStack.EMPTY); + Create.RAILWAYS.markTracksDirty(); + if (box != null) + box.spawnParticles(); + + break; + } + } + } + } + } + + // Only export if we have the condition to + if (shouldDeliverPackages) { + // Export to station + for (int slot = 0; slot < carriageInventory.getSlots(); slot++) { + ItemStack stack = carriageInventory.getStackInSlot(slot); + if (!PackageItem.isPackage(stack)) + continue; + + for (Entry entry : connectedPorts.entrySet()) { + GlobalPackagePort port = entry.getValue(); + BlockPos pos = entry.getKey(); + PostboxBlockEntity box = null; + + if (!PackageItem.matchAddress(stack, port.address)) + continue; + + IItemHandler postboxInventory = port.offlineBuffer; + if (level != null && level.isLoaded(pos) + && level.getBlockEntity(pos) instanceof PostboxBlockEntity ppbe) { + postboxInventory = ppbe.inventory; + box = ppbe; + } + + ItemStack result = ItemHandlerHelper.insertItemStacked(postboxInventory, stack, false); + if (!result.isEmpty()) + continue; + + Create.RAILWAYS.markTracksDirty(); + carriageInventory.setStackInSlot(slot, ItemStack.EMPTY); + if (box != null) + box.spawnParticles(); + + break; + } + } + } + } + } + public void runMailTransfer() { Train train = getPresentTrain(); if (train == null || connectedPorts.isEmpty()) return; Level level = null; + if (train.runtime.schedule != null) { + runMailTransferScheduled(); + return; + } + for (Carriage carriage : train.carriages) { if (level == null) { CarriageContraptionEntity entity = carriage.anyAvailableEntity(); diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index dc0242aeb4..5573682dd1 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -801,7 +801,7 @@ "create.schedule.instruction.address_filter_edit_box_2": "Train may navigate anywhere to collect it", "create.schedule.instruction.address_filter_edit_box_3": "Add another instruction to make it return", - "create.schedule.condition_type": "Continue if/after:", + "create.schedule.condition_type": "Continue if/after:", "create.schedule.condition.editor": "Condition Editor", "create.schedule.condition.delay": "Scheduled Delay", @@ -856,6 +856,14 @@ "create.schedule.condition.redstone_link.frequency_state": "Frequency state:", "create.schedule.condition.redstone_link.frequency_powered": "Frequency powered:", "create.schedule.condition.redstone_link.frequency_unpowered": "Frequency not powered:", + "create.schedule.condition.retrieve_package": "Retrieve Packages", + "create.schedule.condition.retrieve_package.summary": "Retrieve packages", + "create.schedule.condition.retrieve_package.status": "Retrieve packages: %1$s", + "create.schedule.condition.retrieve_package.addressed": "with address %1$s", + "create.schedule.condition.deliver_package": "Deliver Packages", + "create.schedule.condition.deliver_package.summary": "Deliver packages", + "create.schedule.condition.deliver_package.status": "Delivering packages", + "create.schedule.condition.player_count": "Players Seated", "create.schedule.condition.player_count.summary": "%1$s Player", "create.schedule.condition.player_count.summary_plural": "%1$s Players", diff --git a/src/main/resources/assets/create/lang/en_gb.json b/src/main/resources/assets/create/lang/en_gb.json index b4b253b563..ee79623451 100644 --- a/src/main/resources/assets/create/lang/en_gb.json +++ b/src/main/resources/assets/create/lang/en_gb.json @@ -2723,6 +2723,13 @@ "create.schedule.condition.redstone_link.unpowered": "Not powered", "create.schedule.condition.redstone_link_off": "Link Off", "create.schedule.condition.redstone_link_on": "Link On", + "create.schedule.condition.retrieve_package": "Retrieve Packages", + "create.schedule.condition.retrieve_package.summary": "Retrieve packages", + "create.schedule.condition.retrieve_package.status": "Retrieve packages: %1$s", + "create.schedule.condition.retrieve_package.addressed": "with address %1$s", + "create.schedule.condition.deliver_package": "Deliver Packages", + "create.schedule.condition.deliver_package.summary": "Deliver packages", + "create.schedule.condition.deliver_package.status": "Delivering packages", "create.schedule.condition.threshold.anything": "Anything", "create.schedule.condition.threshold.buckets": "Buckets", "create.schedule.condition.threshold.equal": "exactly", @@ -2771,14 +2778,6 @@ "create.schedule.instruction.name_edit_box": "Scheduled Title", "create.schedule.instruction.name_edit_box_1": "Affects text shown on displays", "create.schedule.instruction.name_edit_box_2": "Defaults to next destination's name", - "create.schedule.instruction.package_delivery": "Deliver Package", - "create.schedule.instruction.package_delivery.summary": "Deliver first package in cargo", - "create.schedule.instruction.package_delivery.summary_1": "Drives to the station with", - "create.schedule.instruction.package_delivery.summary_2": "the correct postbox", - "create.schedule.instruction.package_retrieval": "Retrieve Package", - "create.schedule.instruction.package_retrieval.summary": "Fetch a package addressed to:", - "create.schedule.instruction.package_retrieval.summary_1": "Drives to a postbox with", - "create.schedule.instruction.package_retrieval.summary_2": "an undelivered package", "create.schedule.instruction.rename": "Update Schedule Title", "create.schedule.instruction.rename.summary": "New Title:", "create.schedule.instruction.throttle": "Limit Max Speed", @@ -3359,4 +3358,4 @@ "item.create.zinc_nugget": "Zinc Nugget", "itemGroup.create.base": "Create", "itemGroup.create.palettes": "Create's Building Blocks" -} \ No newline at end of file +}