Skip to content

Commit

Permalink
Closes #5165: Better separation for crafting and processing patterns (#…
Browse files Browse the repository at this point in the history
…5485)

* Closes #5165: Better separation for crafting and processing patterns

Both are now their own item id with their own name.
Distinct textures will come later
Update pattern terminal to better reflect the concept of a primary
output. Needs further documentation/hints.

* Add a spotless step to enforce lang json ordering

* Preserve the primary processing output when condensing outputs

* Added the ability to specify tooltip areas through widget styles, and used it to show tooltips for the pattern terminal processing slots.

* Add translation keys for new items.

* Delegate the decoding to the corresponding pattern item, rather than using the same item registered twice. Remove the ability to "re-encode" an existing pattern. They need to be replaced to allow switching types.

* Creates -> Produces for processing patterns

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
Co-authored-by: Sebastian Hartte <shartte@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 10, 2021
1 parent 704eff5 commit 1381eab
Show file tree
Hide file tree
Showing 51 changed files with 1,659 additions and 1,329 deletions.
34 changes: 34 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,40 @@ spotless {
targetExclude 'src/generated/resources/**'
prettier().config(['parser': 'json'])
}

format 'langJsonOrder', {
target 'src/**/lang/*.json'
addStep(LangJsonOrderStep.create())
}
}

import com.diffplug.spotless.FormatterFunc
import com.diffplug.spotless.FormatterStep
import com.google.gson.Gson
import com.google.gson.GsonBuilder

/**
* Enforce key ordering in the lang files by parsing the JSON to a TreeMap and writing it back.
*/
final class LangJsonOrderStep {
private LangJsonOrderStep() {}
private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().setLenient().setPrettyPrinting().create();

static FormatterStep create() {
return FormatterStep.createNeverUpToDate(
'langJsonOrderStep',
new FormatterFunc() {
String apply(String input) {
TreeMap<String, Object> jsonMap = GSON.fromJson(input, TreeMap.class);
String sortedJson = GSON.toJson(jsonMap);
// Unescape ' (the apostrophe character). GSON escapes it by default.
String prettyPrinted = sortedJson.replace('\\u0027', '\'');
// Add a return after the last line to respect prettier's formatting of the json files.
return prettyPrinted + "\n";
}
},
);
}
}

////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

/**
* Allows mod to decode their {@link IPatternDetails} from their item stacks. This is required for custom patterns,
* otherwise the crafting CPU can't properly persist them. Register a single instance to {@link ICraftingHelper}.
* otherwise the crafting CPU can't properly persist them. Register a single instance to {@link IPatternDetailsHelper}.
*/
public interface IPatternDetailsDecoder {
boolean isEncodedPattern(ItemStack stack);
Expand Down
11 changes: 4 additions & 7 deletions src/main/java/appeng/api/crafting/IPatternDetailsHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,15 @@ default IPatternDetails decodePattern(ItemStack stack, Level level) {
* Encodes a processing pattern which represents the ability to convert the given inputs into the given outputs
* using some process external to the ME system.
*
* @param stack If null, a new item will be created to hold the encoded pattern. Otherwise the given item must
* already contains an encoded pattern that will be overwritten.
* @throws IllegalArgumentException If either in or out contain only empty ItemStacks.
* @param out The first element is considered the primary output and must be present
* @throws IllegalArgumentException If either in or out contain only empty ItemStacks, or no primary output
* @return A new encoded pattern, or the given stack with the pattern encoded in it.
*/
ItemStack encodeProcessingPattern(@Nullable ItemStack stack, IAEStack[] in, IAEStack[] out);
ItemStack encodeProcessingPattern(IAEStack[] in, IAEStack[] out);

/**
* Encodes a crafting pattern which represents a Vanilla crafting recipe.
*
* @param stack If null, a new item will be created to hold the encoded pattern. Otherwise the given item
* must already contains an encoded pattern that will be overwritten.
* @param recipe The Vanilla crafting recipe to be encoded.
* @param in The items in the crafting grid, which are used to determine what items are supplied from
* the ME system to craft using this pattern.
Expand All @@ -68,6 +65,6 @@ default IPatternDetails decodePattern(ItemStack stack, Level level) {
* recipe.
* @throws IllegalArgumentException If either in or out contain only empty ItemStacks.
*/
ItemStack encodeCraftingPattern(@Nullable ItemStack stack, CraftingRecipe recipe, ItemStack[] in, ItemStack out,
ItemStack encodeCraftingPattern(CraftingRecipe recipe, ItemStack[] in, ItemStack out,
boolean allowSubstitutes);
}
5 changes: 3 additions & 2 deletions src/main/java/appeng/api/ids/AEItemIds.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public final class AEItemIds {
public static final ResourceLocation NETWORK_TOOL = id("network_tool");
public static final ResourceLocation VIEW_CELL = id("view_cell");
public static final ResourceLocation MEMORY_CARD = id("memory_card");
public static final ResourceLocation ENCODED_PATTERN = id("encoded_pattern");
public static final ResourceLocation BLANK_PATTERN = id("blank_pattern");
public static final ResourceLocation CRAFTING_PATTERN = id("crafting_pattern");
public static final ResourceLocation PROCESSING_PATTERN = id("processing_pattern");
public static final ResourceLocation BIOMETRIC_CARD = id("biometric_card");
public static final ResourceLocation ENTROPY_MANIPULATOR = id("entropy_manipulator");
public static final ResourceLocation MATTER_CANNON = id("matter_cannon");
Expand Down Expand Up @@ -218,7 +220,6 @@ public final class AEItemIds {
public static final ResourceLocation ENDER_DUST = id("ender_dust");
public static final ResourceLocation SINGULARITY = id("singularity");
public static final ResourceLocation QUANTUM_ENTANGLED_SINGULARITY = id("quantum_entangled_singularity");
public static final ResourceLocation BLANK_PATTERN = id("blank_pattern");

private static ResourceLocation id(String id) {
return new ResourceLocation(AEConstants.MOD_ID, id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@
import appeng.core.sync.packets.AssemblerAnimationPacket;
import appeng.crafting.CraftingEvent;
import appeng.crafting.pattern.AECraftingPattern;
import appeng.crafting.pattern.AEPatternDecoder;
import appeng.items.misc.EncodedPatternItem;
import appeng.crafting.pattern.CraftingPatternItem;
import appeng.menu.NullMenu;
import appeng.parts.automation.DefinitionUpgradeInventory;
import appeng.parts.automation.UpgradeInventory;
Expand Down Expand Up @@ -244,14 +243,13 @@ public CompoundTag save(final CompoundTag data) {
public void load(final CompoundTag data) {
super.load(data);
if (data.contains("myPlan")) {
final ItemStack myPat = ItemStack.of(data.getCompound("myPlan"));
var myPat = ItemStack.of(data.getCompound("myPlan"));

if (!myPat.isEmpty() && myPat.getItem() instanceof EncodedPatternItem) {
final Level level = this.getLevel();
var details = AEPatternDecoder.INSTANCE.decodePattern(myPat, level, false);
if (details instanceof AECraftingPattern craftingPattern) {
if (!myPat.isEmpty() && myPat.getItem() instanceof CraftingPatternItem patternItem) {
var details = patternItem.decode(myPat, getLevel(), false);
if (details != null) {
this.forcePlan = true;
this.myPlan = craftingPattern;
this.myPlan = details;
this.pushDirection = Direction.values()[data.getInt("pushDirection")];
}
}
Expand All @@ -270,15 +268,15 @@ private void recalculatePlan() {

final ItemStack is = this.patternInv.getStackInSlot(0);

if (!is.isEmpty() && is.getItem() instanceof EncodedPatternItem) {
if (!is.isEmpty() && is.getItem() instanceof CraftingPatternItem patternItem) {
if (!ItemStack.isSame(is, this.myPattern)) {
final Level level = this.getLevel();
var details = AEPatternDecoder.INSTANCE.decodePattern(is, level, false);
var details = patternItem.decode(is, level, false);

if (details instanceof AECraftingPattern craftingPattern) {
if (details != null) {
this.progress = 0;
this.myPattern = is;
this.myPlan = craftingPattern;
this.myPlan = details;
}
}
} else {
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/appeng/client/gui/WidgetContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class WidgetContainer {
private final ScreenStyle style;
private final Map<String, AbstractWidget> widgets = new HashMap<>();
private final Map<String, ICompositeWidget> compositeWidgets = new HashMap<>();
private final Map<String, ResolvedTooltipArea> tooltips = new HashMap<>();

public WidgetContainer(ScreenStyle style) {
this.style = style;
Expand Down Expand Up @@ -149,6 +150,17 @@ void populateScreen(Consumer<AbstractWidget> addWidget, Rect2i bounds, AEBaseScr

widget.populateScreen(addWidget, bounds, screen);
}

tooltips.clear();
for (var entry : style.getTooltips().entrySet()) {
var pos = entry.getValue().resolve(relativeBounds);
var area = new Rect2i(
pos.getX(), pos.getY(),
entry.getValue().getWidth(),
entry.getValue().getHeight());
tooltips.put(entry.getKey(), new ResolvedTooltipArea(
area, new Tooltip(entry.getValue().getTooltip())));
}
}

/**
Expand Down Expand Up @@ -264,6 +276,15 @@ private void openPriorityGui() {
NetworkHandler.instance().sendToServer(new SwitchGuisPacket(PriorityMenu.TYPE));
}

/**
* Enables or disables a tooltip area that is defined in the widget styles.
*/
public void setTooltipAreaEnabled(String id, boolean enabled) {
var tooltip = tooltips.get(id);
Preconditions.checkArgument(tooltip != null, "No tooltip with id '%s' is defined");
tooltip.enabled = enabled;
}

@Nullable
public Tooltip getTooltip(int mouseX, int mouseY) {
for (ICompositeWidget c : this.compositeWidgets.values()) {
Expand All @@ -277,6 +298,30 @@ public Tooltip getTooltip(int mouseX, int mouseY) {
}
}

for (var tooltipArea : tooltips.values()) {
if (tooltipArea.enabled && contains(tooltipArea.area, mouseX, mouseY)) {
return tooltipArea.tooltip;
}
}

return null;
}

// NOTE: Vanilla's implementation of Rect2i is broken since it uses less-than-equal to compare against x+width,
// rather than less-than.
private static boolean contains(Rect2i area, int mouseX, int mouseY) {
return mouseX >= area.getX() && mouseX < area.getX() + area.getWidth()
&& mouseY >= area.getY() && mouseY < area.getY() + area.getHeight();
}

private static class ResolvedTooltipArea {
private final Rect2i area;
private final Tooltip tooltip;
private boolean enabled = true;

public ResolvedTooltipArea(Rect2i area, Tooltip tooltip) {
this.area = area;
this.tooltip = tooltip;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

import appeng.items.misc.EncodedPatternItem;
import appeng.crafting.pattern.EncodedPatternItem;
import appeng.menu.slot.AppEngSlot;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ protected void updateBeforeRender() {
}

setSlotsHidden(SlotSemantic.CRAFTING_RESULT, !this.menu.isCraftingMode());
setSlotsHidden(SlotSemantic.PROCESSING_RESULT, this.menu.isCraftingMode());
setSlotsHidden(SlotSemantic.PROCESSING_PRIMARY_RESULT, this.menu.isCraftingMode());
setSlotsHidden(SlotSemantic.PROCESSING_FIRST_OPTIONAL_RESULT, this.menu.isCraftingMode());
setSlotsHidden(SlotSemantic.PROCESSING_SECOND_OPTIONAL_RESULT, this.menu.isCraftingMode());

// Only show tooltips for the processing output slots, if we're in processing mode
widgets.setTooltipAreaEnabled("processing-primary-output", !this.menu.isCraftingMode());
widgets.setTooltipAreaEnabled("processing-optional-output1", !this.menu.isCraftingMode());
widgets.setTooltipAreaEnabled("processing-optional-output2", !this.menu.isCraftingMode());

// If the menu allows converting items to fluids, show the button
this.convertItemsToFluidsBtn.visible = this.menu.canConvertItemsToFluids();
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/appeng/client/gui/style/ScreenStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public class ScreenStyle {

private final Map<String, WidgetStyle> widgets = new HashMap<>();

private final Map<String, TooltipArea> tooltips = new HashMap<>();

public Color getColor(PaletteColor color) {
return palette.get(color);
}
Expand All @@ -92,6 +94,10 @@ public Map<String, Text> getText() {
return text;
}

public Map<String, TooltipArea> getTooltips() {
return tooltips;
}

public Blitter getBackground() {
return background != null ? background.copy() : null;
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/appeng/client/gui/style/TooltipArea.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package appeng.client.gui.style;

import java.util.ArrayList;
import java.util.List;

import net.minecraft.network.chat.Component;

public final class TooltipArea extends Position {
private int width;
private int height;

private List<Component> tooltip = new ArrayList<>();

public List<Component> getTooltip() {
return tooltip;
}

public void setTooltip(List<Component> tooltip) {
this.tooltip = tooltip;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}
}
37 changes: 4 additions & 33 deletions src/main/java/appeng/core/api/ApiPatternDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,16 @@

import javax.annotation.Nullable;

import com.google.common.base.Preconditions;

import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingRecipe;
import net.minecraft.world.level.Level;

import appeng.api.crafting.IPatternDetails;
import appeng.api.crafting.IPatternDetailsDecoder;
import appeng.api.crafting.IPatternDetailsHelper;
import appeng.api.storage.StorageChannels;
import appeng.api.storage.data.IAEStack;
import appeng.core.definitions.AEItems;
import appeng.crafting.pattern.AEPatternDecoder;
import appeng.crafting.pattern.AEPatternHelper;

public class ApiPatternDetails implements IPatternDetailsHelper {
private final List<IPatternDetailsDecoder> decoders = new CopyOnWriteArrayList<>();
Expand Down Expand Up @@ -76,39 +72,14 @@ public IPatternDetails decodePattern(ItemStack stack, Level level, boolean autoR
}

@Override
public ItemStack encodeCraftingPattern(@Nullable ItemStack stack, CraftingRecipe recipe, ItemStack[] in,
public ItemStack encodeCraftingPattern(CraftingRecipe recipe, ItemStack[] in,
ItemStack out, boolean allowSubstitutes) {
if (stack == null) {
stack = AEItems.ENCODED_PATTERN.stack();
} else {
Preconditions.checkArgument(isEncodedPattern(stack));
}

AEPatternHelper.encodeCraftingPattern(stack, recipe, in, out, allowSubstitutes);
return stack;
return AEItems.CRAFTING_PATTERN.asItem().encode(recipe, in, out, allowSubstitutes);
}

@Override
public ItemStack encodeProcessingPattern(@Nullable ItemStack stack, IAEStack[] in, IAEStack[] out) {
checkItemsOrFluids(in);
checkItemsOrFluids(out);
if (stack == null) {
stack = AEItems.ENCODED_PATTERN.stack();
} else {
Preconditions.checkArgument(isEncodedPattern(stack));
}

AEPatternHelper.encodeProcessingPattern(stack, in, out);
return stack;
public ItemStack encodeProcessingPattern(IAEStack[] in, IAEStack[] out) {
return AEItems.PROCESSING_PATTERN.asItem().encode(in, out);
}

private static void checkItemsOrFluids(IAEStack[] stacks) {
for (var stack : stacks) {
if (stack != null) {
if (stack.getChannel() != StorageChannels.items() && stack.getChannel() != StorageChannels.fluids()) {
throw new IllegalArgumentException("Unsupported storage channel: " + stack.getChannel());
}
}
}
}
}
Loading

0 comments on commit 1381eab

Please sign in to comment.