Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ dependencies {
}
}

// Integrated Tunnels is only needed to set up networks with item storage in game tests.
implementation ("org.cyclops.integratedtunnels:integratedtunnels-${project.minecraft_version}-neoforge:${project.integratedtunnels_version}:deobf") {
transitive = false
}

// Add something like 'integratedterminalscompat_version_local=0.1.0-DEV' to your secrets.properties if you want to use a custom local Integrated Tunnels Compat version.
if(secrets.integratedterminalscompat_version_local) {
shadow("org.cyclops.integratedterminalscompat:integratedterminalscompat-${project.minecraft_version}-neoforge:${secrets.integratedterminalscompat_version_local}") {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ cyclopscore_version=1.26.2-808
integrateddynamics_version=1.32.0-1630
integratedterminalscompat_version=1.0.0-167
integratedcrafting_version=1.4.1-442
integratedtunnels_version=1.8.44-484
commoncapabilities_version=2.9.12-263
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public interface ITerminalStorageTabServer {

/**
* Called on each tick this tab is active.
* @param channel The channel that is being shown in the terminal.
*/
public void updateActive();
public void updateActive(int channel);

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,51 @@ public enum TerminalCraftingJobStatus {
/**
* A generic job error state.
*/
ERROR(Helpers.RGBAToInt(250, 0, 0, 150), false),
ERROR(Helpers.RGBAToInt(250, 0, 0, 150), false, 5),
/**
* If this job, or its dependencies, have missing storage instances.
*/
INVALID(Helpers.RGBAToInt(250, 10, 13, 150), false),
INVALID(Helpers.RGBAToInt(250, 10, 13, 150), false, 5),
/**
* No outputs have been crafted yet, and they are not scheduled yet for crafting.
*/
UNSTARTED(Helpers.RGBAToInt(225, 225, 225, 150), true),
UNSTARTED(Helpers.RGBAToInt(225, 225, 225, 150), true, 1),
/**
* The crafting job has been scheduled,
* but is not processing yet because other jobs are still processing.
*/
QUEUEING(Helpers.RGBAToInt(243, 245, 150, 150), true),
QUEUEING(Helpers.RGBAToInt(243, 245, 150, 150), true, 2),
/**
* The crafting job has been scheduled,
* but is not processing yet because a dependency is still being processed.
*/
PENDING_DEPENDENCIES(Helpers.RGBAToInt(243, 245, 4, 150), true),
PENDING_DEPENDENCIES(Helpers.RGBAToInt(243, 245, 4, 150), true, 2),
/**
* The crafting job has been scheduled,
* but is not processing yet because input ingredients are missing.
*/
PENDING_INPUTS(Helpers.RGBAToInt(245, 172, 3, 150), true),
PENDING_INPUTS(Helpers.RGBAToInt(245, 172, 3, 150), true, 4),
/**
* The recipe inputs could not be inserted into the crafting handler.
*/
INVALID_INPUTS(Helpers.RGBAToInt(250, 10, 13, 150), true),
INVALID_INPUTS(Helpers.RGBAToInt(250, 10, 13, 150), true, 5),
/**
* The output is actively being crafted.
*/
CRAFTING(Helpers.RGBAToInt(43, 174, 231, 150), true),
CRAFTING(Helpers.RGBAToInt(43, 174, 231, 150), true, 3),
/**
* All expected outputs are crafted.
*/
FINISHED(Helpers.RGBAToInt(43, 231, 47, 150), true);
FINISHED(Helpers.RGBAToInt(43, 231, 47, 150), true, 0);

private final int color;
private final boolean valid;
private final int priority;

private TerminalCraftingJobStatus(int color, boolean valid) {
private TerminalCraftingJobStatus(int color, boolean valid, int priority) {
this.color = color;
this.valid = valid;
this.priority = priority;
}

public int getColor() {
Expand All @@ -62,4 +64,16 @@ public int getColor() {
public boolean isValid() {
return valid;
}

/**
* The relevance of this status when a single ingredient is produced by multiple crafting jobs,
* in which case only the status with the highest priority is shown.
*
* Statuses that require the attention of the player take precedence over statuses that don't.
*
* @return The priority, where a higher number indicates a higher priority.
*/
public int getPriority() {
return priority;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,14 @@ public class Images {
public static final Image BUTTON_SMALL_OVERLAY_CROSS = new Image(ICONS, 0, 44, 8, 8);
public static final Image BUTTON_SMALL_OVERLAY_SQUARE = new Image(ICONS, 8, 44, 8, 8);

/**
* The animation frames of the spinner that is shown on ingredients that are being crafted.
*/
public static final Image[] SPINNER = new Image[8];
static {
for (int frame = 0; frame < SPINNER.length; frame++) {
SPINNER[frame] = new Image(ICONS, frame * 8, 64, 8, 8);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
import org.cyclops.integratedterminals.core.terminalstorage.button.TerminalButtonScaleGui;
import org.cyclops.integratedterminals.core.terminalstorage.button.TerminalButtonSort;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.HandlerWrappedTerminalCraftingOption;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutput;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutputEntry;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutputs;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.TerminalStorageTabIngredientCraftingHandlers;
import org.cyclops.integratedterminals.core.terminalstorage.query.IIngredientQuery;
import org.cyclops.integratedterminals.core.terminalstorage.slot.TerminalStorageSlotIngredient;
Expand Down Expand Up @@ -106,6 +109,7 @@ public class TerminalStorageTabIngredientComponentClient<T, M>
private final Int2ObjectMap<List<InstanceWithMetadata<T>>> filteredIngredientsViews;
private final Int2ObjectMap<List<InstanceWithMetadata<T>>> lastFilteredIngredientsViews;
private final Int2ObjectMap<Collection<HandlerWrappedTerminalCraftingOption<T>>> craftingOptions;
private PendingCraftingJobOutputs<T, M> pendingCraftingJobOutputs;

private final Int2LongMap maxQuantities;
private final Int2LongMap totalQuantities;
Expand Down Expand Up @@ -152,6 +156,8 @@ public TerminalStorageTabIngredientComponentClient(ContainerTerminalStorageBase
this.filteredIngredientsViews = new Int2ObjectOpenHashMap<>();
this.lastFilteredIngredientsViews = new Int2ObjectOpenHashMap<>();
this.craftingOptions = new Int2ObjectOpenHashMap<>();
this.pendingCraftingJobOutputs = new PendingCraftingJobOutputs<>(this.ingredientComponent,
IPositionedAddonsNetwork.WILDCARD_CHANNEL);

this.maxQuantities = new Int2LongOpenHashMap();
this.totalQuantities = new Int2LongOpenHashMap();
Expand Down Expand Up @@ -305,6 +311,53 @@ public Collection<HandlerWrappedTerminalCraftingOption<T>> getCraftingOptions(in
return craftingOptions.get(channel);
}

/**
* Called by the server when the outputs that running crafting jobs are still expected to produce have changed.
* @param channel The channel the outputs were collected for.
* @param entries All pending crafting job outputs of all ingredient components.
*/
public synchronized void setPendingCraftingJobOutputs(int channel, List<PendingCraftingJobOutputEntry> entries) {
PendingCraftingJobOutputs<T, M> pendingCraftingJobOutputs = new PendingCraftingJobOutputs<>(this.ingredientComponent, channel);
for (PendingCraftingJobOutputEntry entry : entries) {
if (entry.ingredient().getComponent() == this.ingredientComponent) {
pendingCraftingJobOutputs.add((T) entry.ingredient().getPrototype(), entry.status());
}
}
this.pendingCraftingJobOutputs = pendingCraftingJobOutputs;
}

/**
* Get the quantity and status of the running crafting jobs that will produce the given instance.
* @param channel A channel id.
* @param instance An instance.
* @return The pending crafting job output, or null if the given instance is not being crafted.
*/
@Nullable
public PendingCraftingJobOutput<T> getPendingCraftingJobOutput(int channel, T instance) {
// The outputs are collected for the channel that is shown in the terminal,
// so they don't apply anymore right after the shown channel has changed.
return this.pendingCraftingJobOutputs.getChannel() == channel
? this.pendingCraftingJobOutputs.get(instance) : null;
}

/**
* Check if the given instance is currently shown as a stored ingredient in the given channel.
*
* An instance can be shown twice: once as a stored ingredient, and once for each crafting option producing it.
* This allows crafting option slots to defer to the stored ingredient slot for things
* that apply to the instance as a whole, such as the indication of running crafting jobs.
*
* @param channel A channel id.
* @param instance An instance.
* @return If a stored ingredient slot is shown for the given instance.
*/
public boolean isShownAsStoredInstance(int channel, T instance) {
IIngredientMatcher<T, M> matcher = this.ingredientComponent.getMatcher();
return getInstanceFilterMetadata().test(new InstanceWithMetadata<>(instance, null))
&& getRawUnfilteredIngredientsView(channel)
.contains(instance, matcher.getExactMatchNoQuantityCondition());
}

public List<InstanceWithMetadata<T>> createUnfilteredIngredientsView(int channel) {
// Convert raw ingredients view to list
List<InstanceWithMetadata<T>> enrichedIngredients = Lists.newArrayList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.cyclops.integrateddynamics.api.ingredient.IIngredientPositionsIndex;
import org.cyclops.integrateddynamics.api.ingredient.capability.IIngredientComponentValueHandler;
import org.cyclops.integrateddynamics.api.network.INetwork;
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueHelpers;
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeBoolean;
Expand All @@ -45,8 +46,10 @@
import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingOption;
import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalStorageTabIngredientCraftingHandler;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.HandlerWrappedTerminalCraftingOption;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.PendingCraftingJobOutputs;
import org.cyclops.integratedterminals.core.terminalstorage.crafting.TerminalStorageTabIngredientCraftingHandlers;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientChangeEventPacket;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientCraftingJobsPacket;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientCraftingOptionsPacket;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientMaxQuantityPacket;
import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientUpdateActiveStorageIngredientPacket;
Expand Down Expand Up @@ -85,6 +88,9 @@ public class TerminalStorageTabIngredientComponentServer<T, M> implements ITermi
private final Int2ObjectMap<IngredientCollectionDiffManager<T, M>> filteredDiffManagers;
private boolean initialized; // True if the first change event has been sent to the client.
private boolean sentCraftingOptionsFiltered;
private long nextCraftingJobsUpdate;
private int craftingJobsChannel; // The channel the last pending crafting job outputs were collected for.
private boolean sentCraftingJobs; // True if a non-empty set of pending crafting job outputs was sent to the client.

public TerminalStorageTabIngredientComponentServer(ResourceLocation name, INetwork network,
IngredientComponent<T, M> ingredientComponent,
Expand All @@ -102,6 +108,9 @@ public TerminalStorageTabIngredientComponentServer(ResourceLocation name, INetwo
this.ingredientsFilter = null;
this.unfilteredIngredientsViews = new Int2ObjectOpenHashMap<>();
this.filteredDiffManagers = new Int2ObjectOpenHashMap<>();
this.nextCraftingJobsUpdate = 0;
this.craftingJobsChannel = IPositionedAddonsNetwork.WILDCARD_CHANNEL;
this.sentCraftingJobs = false;

// Schedule an observation on creation, as the channel may not have been indexed yet.
ingredientNetwork.scheduleObservation();
Expand Down Expand Up @@ -169,8 +178,42 @@ public void deInit() {
}

@Override
public void updateActive() {
public void updateActive(int channel) {
this.ingredientNetwork.scheduleObservation();
updatePendingCraftingJobOutputs(channel);
}

/**
* Collect the outputs that all running crafting jobs are still expected to produce,
* and send them to the client, so that they can be indicated in the storage terminal.
*
* As crafting job statuses change frequently,
* this is throttled by {@link GeneralConfig#guiTerminalCraftingJobsUpdateFrequency}.
*
* @param channel The channel that is being shown in the terminal.
*/
protected void updatePendingCraftingJobOutputs(int channel) {
// Don't wait for the next update when the shown channel changed, as the client has no outputs for it yet.
if (channel == this.craftingJobsChannel && System.currentTimeMillis() < this.nextCraftingJobsUpdate) {
return;
}
this.craftingJobsChannel = channel;
this.nextCraftingJobsUpdate = System.currentTimeMillis() + GeneralConfig.guiTerminalCraftingJobsUpdateFrequency;

PendingCraftingJobOutputs<T, M> pendingCraftingJobOutputs = PendingCraftingJobOutputs
.collectFromNetwork(this.ingredientComponent, this.network, channel);

// Don't send anything as long as no crafting jobs are running,
// but do send one final (empty) update once the last job has finished.
boolean hasCraftingJobs = !pendingCraftingJobOutputs.isEmpty();
if (!hasCraftingJobs && !this.sentCraftingJobs) {
return;
}
this.sentCraftingJobs = hasCraftingJobs;

IntegratedTerminals._instance.getPacketHandler().sendToPlayer(
new TerminalStorageIngredientCraftingJobsPacket(player.level().registryAccess(),
this.getName().toString(), pendingCraftingJobOutputs), player);
}

protected IIngredientCollapsedCollectionMutable<T, M> getUnfilteredIngredientsView(int channel) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.cyclops.integratedterminals.core.terminalstorage.crafting;

import org.cyclops.integratedterminals.api.terminalstorage.crafting.TerminalCraftingJobStatus;

/**
* The aggregated pending output of all running crafting jobs for a single ingredient instance.
* @param <T> The instance type.
* @author rubensworks
*/
public class PendingCraftingJobOutput<T> {

private final T instance;
private final TerminalCraftingJobStatus status;

public PendingCraftingJobOutput(T instance, TerminalCraftingJobStatus status) {
this.instance = instance;
this.status = status;
}

/**
* @return The pending instance, where the quantity indicates how much is still expected to be crafted.
*/
public T getInstance() {
return instance;
}

/**
* @return The most relevant status over all crafting jobs that will produce this instance.
*/
public TerminalCraftingJobStatus getStatus() {
return status;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cyclops.integratedterminals.core.terminalstorage.crafting;

import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
import org.cyclops.integratedterminals.api.terminalstorage.crafting.TerminalCraftingJobStatus;

/**
* A single component-agnostic pending crafting job output, as it is sent from server to client.
*
* @param ingredient The pending output, where the quantity indicates how much is still expected to be crafted.
* @param status The status of the crafting jobs that will produce the ingredient.
* @author rubensworks
*/
public record PendingCraftingJobOutputEntry(IPrototypedIngredient<?, ?> ingredient, TerminalCraftingJobStatus status) {
}
Loading
Loading