mirror of
https://github.com/GTNewHorizons/JustEnoughCalculation.git
synced 2026-11-14 01:04:06 +00:00
feat: calculation logic start
This commit is contained in:
1 parent
477a84804e
commit
37a94075c7
23 files changed
+273
-192
No files matched your search
@@ -1,52 +1,67 @@
|
||||
package me.towdium.jecalculation.algorithm;
|
||||
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
import me.towdium.jecalculation.data.label.ILabel;
|
||||
import me.towdium.jecalculation.data.structure.Recipe;
|
||||
import me.towdium.jecalculation.utils.wrappers.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CostList {
|
||||
ArrayList<ILabel> labels;
|
||||
List<ILabel> labels;
|
||||
|
||||
public CostList() {
|
||||
labels = new ArrayList<>();
|
||||
}
|
||||
|
||||
public CostList(List<ILabel> labels) {
|
||||
this.labels = new ArrayList<>(labels);
|
||||
this.labels = labels.stream().map(ILabel::copy).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public CostList(List<ILabel> positive, List<ILabel> negative) {
|
||||
labels = new ArrayList<>(positive);
|
||||
labels = positive.stream().map(ILabel::copy).collect(Collectors.toList());
|
||||
negative.forEach(i -> {
|
||||
ILabel l = i.copy();
|
||||
labels.add(l.invertAmount());
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public CostList merge(CostList costList, boolean add) {
|
||||
costList.labels.forEach(i -> labels.add(add ? i.copy() : i.copy().invertAmount()));
|
||||
return cancel();
|
||||
public CostList(Recipe recipe) {
|
||||
this(Arrays.asList(recipe.getLabel(Recipe.enumIoType.OUTPUT)),
|
||||
Arrays.asList(recipe.getLabel(Recipe.enumIoType.INPUT)));
|
||||
}
|
||||
|
||||
private CostList cancel() {
|
||||
ArrayList<ILabel> cancelled = new ArrayList<>();
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public CostList merge(CostList costList, boolean add, boolean strict) {
|
||||
CostList ret = copy();
|
||||
costList.labels.forEach(i -> ret.labels.add(add ? i.copy() : i.copy().invertAmount()));
|
||||
ret.cancel(strict);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void cancel(boolean strict) {
|
||||
for (int i = 0; i < labels.size(); i++) {
|
||||
for (int j = i + 1; j < labels.size(); j++) {
|
||||
Optional<Pair<ILabel, ILabel>> l = ILabel.MERGER.merge(labels.get(i), labels.get(j), true);
|
||||
if (l.isPresent()) {
|
||||
labels.set(i, l.get().one);
|
||||
labels.set(j, ILabel.EMPTY);
|
||||
cancelled.add(l.get().two);
|
||||
for (int j = 0; j < labels.size(); j++) {
|
||||
if (i == j)
|
||||
continue;
|
||||
if (strict) {
|
||||
ILabel a = labels.get(i);
|
||||
ILabel b = labels.get(j);
|
||||
if (a.matches(b)) {
|
||||
labels.set(i, a.setAmount(a.getAmount() + b.getAmount()));
|
||||
labels.set(j, ILabel.EMPTY);
|
||||
}
|
||||
} else {
|
||||
Optional<ILabel> l = ILabel.MERGER.merge(labels.get(i), labels.get(j), true);
|
||||
if (l.isPresent()) {
|
||||
labels.set(i, l.get());
|
||||
labels.set(j, ILabel.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
labels = labels.stream().filter(i -> i != ILabel.EMPTY).collect(Collectors.toCollection(ArrayList::new));
|
||||
return new CostList(cancelled);
|
||||
labels = labels.stream().filter(i -> i != ILabel.EMPTY).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,22 +69,94 @@ public class CostList {
|
||||
if (obj instanceof CostList) {
|
||||
CostList c = (CostList) obj;
|
||||
CostList m = c.copy();
|
||||
m.merge(this, false);
|
||||
return m.labels.isEmpty();
|
||||
return m.merge(this, false, true).labels.isEmpty();
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public CostList copy() {
|
||||
CostList ret = new CostList();
|
||||
ret.labels = labels.stream().map(ILabel::copy).collect(Collectors.toCollection(ArrayList::new));
|
||||
ret.labels = labels.stream().map(ILabel::copy).collect(Collectors.toList());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public boolean isEmpty() {
|
||||
return labels.isEmpty();
|
||||
}
|
||||
|
||||
public ArrayList<ILabel> getLabels() {
|
||||
public List<ILabel> getLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
||||
public Calculator calculate() {
|
||||
return new Calculator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 0;
|
||||
for (ILabel i : labels)
|
||||
hash ^= i.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static class Calculator {
|
||||
CostList start;
|
||||
ArrayList<Pair<CostList, CostList>> procedure;
|
||||
List<ILabel> catalysts;
|
||||
private int index;
|
||||
|
||||
public Calculator(CostList cl) {
|
||||
HashSet<CostList> set = new HashSet<>();
|
||||
start = cl;
|
||||
set.add(start);
|
||||
Recipe next = find(true);
|
||||
while (next != null) {
|
||||
CostList original = getCurrent();
|
||||
CostList difference = new CostList(next);
|
||||
CostList result = original.merge(difference, false, false);
|
||||
if (set.contains(result))
|
||||
next = find(false);
|
||||
else {
|
||||
set.add(result);
|
||||
procedure.add(new Pair<>(result, difference));
|
||||
addCatalyst(next.getLabel(Recipe.enumIoType.CATALYST));
|
||||
next = find(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Recipe find(boolean reset) {
|
||||
if (reset)
|
||||
index = 0;
|
||||
List<ILabel> labels = getCurrent().labels;
|
||||
for (; index < labels.size(); index++) {
|
||||
ILabel label = labels.get(index);
|
||||
if (label.getAmount() >= 0)
|
||||
continue;
|
||||
Optional<Recipe> recipe = Controller.getRecipe(label, Recipe.enumIoType.OUTPUT);
|
||||
if (recipe.isPresent())
|
||||
return recipe.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addCatalyst(ILabel[] labels) {
|
||||
LOOP:
|
||||
for (ILabel i : labels) {
|
||||
for (ILabel j : catalysts) {
|
||||
if (j.matches(i)) {
|
||||
j.setAmount(j.getAmount() + i.getAmount());
|
||||
continue LOOP;
|
||||
}
|
||||
}
|
||||
catalysts.add(i.copy());
|
||||
}
|
||||
}
|
||||
|
||||
private CostList getCurrent() {
|
||||
return procedure.isEmpty() ? start : procedure.get(procedure.size() - 1).one;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -26,7 +27,7 @@ import java.util.stream.Collectors;
|
||||
* Date: 17-10-15.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class ControllerClient {
|
||||
public class Controller {
|
||||
public static final String KEY_RECIPES = "recipes";
|
||||
public static final String KEY_RECENTS = "recents";
|
||||
static Recipes recipesClient;
|
||||
@@ -71,11 +72,11 @@ public class ControllerClient {
|
||||
return getRecord().getRecipes(group);
|
||||
}
|
||||
|
||||
public static List<Triple<Recipe, String, Integer>> getRecipes(ILabel label, Recipe.enumIoType type) {
|
||||
return getRecord().getRecipes(label, type);
|
||||
public static Optional<Recipe> getRecipe(ILabel label, Recipe.enumIoType type) {
|
||||
return getRecord().getRecipe(label, type);
|
||||
}
|
||||
|
||||
public static List<ILabel> getRecent() {
|
||||
public static List<ILabel> getRecent() { // TODO reduce usage of it since it reads nbt again
|
||||
return recentsClient.getRecords();
|
||||
}
|
||||
|
||||
+1
-3
@@ -1,11 +1,9 @@
|
||||
package me.towdium.jecalculation.data.capacity;
|
||||
package me.towdium.jecalculation.data.capability;
|
||||
|
||||
import me.towdium.jecalculation.JustEnoughCalculation;
|
||||
import me.towdium.jecalculation.data.structure.Recipes;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.IExtendedEntityProperties;
|
||||
|
||||
@@ -54,6 +54,8 @@ public interface ILabel {
|
||||
|
||||
ILabel invertAmount();
|
||||
|
||||
int getAmount();
|
||||
|
||||
static void initClient() {
|
||||
CONVERTER.register(LOreDict::guess);
|
||||
EDITOR.register(PickerSimple.FluidStack::new, "fluid_stack", new LFluidStack(1000, FluidRegistry.WATER));
|
||||
@@ -63,14 +65,6 @@ public interface ILabel {
|
||||
MERGER.register("oreDict", "oreDict", LOreDict::mergeOreDict);
|
||||
}
|
||||
|
||||
int getAmount();
|
||||
|
||||
|
||||
String getAmountString();
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
String getDisplayName();
|
||||
|
||||
static void initServer() {
|
||||
DESERIALIZER.register(LFluidStack.IDENTIFIER, LFluidStack::new);
|
||||
DESERIALIZER.register(LItemStack.IDENTIFIER, LItemStack::new);
|
||||
@@ -79,25 +73,25 @@ public interface ILabel {
|
||||
DESERIALIZER.register(LEmpty.IDENTIFIER, i -> EMPTY);
|
||||
}
|
||||
|
||||
String getAmountString();
|
||||
|
||||
String getDisplayName();
|
||||
|
||||
List<String> getToolTip(List<String> existing, boolean detailed);
|
||||
|
||||
ILabel copy();
|
||||
|
||||
NBTTagCompound toNBTTagCompound();
|
||||
|
||||
void setAmount(int amount);
|
||||
|
||||
String getIdentifier();
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
default void drawLabel(JecaGui gui, int xPos, int yPos, boolean center) {
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.translate(center ? xPos - 8 : xPos, center ? yPos - 8 : yPos, 0);
|
||||
drawLabel(gui);
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
ILabel setAmount(int amount);
|
||||
|
||||
boolean matches(Object l);
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
void drawLabel(JecaGui gui, int xPos, int yPos, boolean center);
|
||||
|
||||
void drawLabel(JecaGui gui);
|
||||
|
||||
/**
|
||||
* Since {@link ILabel} merging is bidirectional, it is redundant to
|
||||
@@ -118,14 +112,15 @@ public interface ILabel {
|
||||
functions.add(a, b, func);
|
||||
}
|
||||
|
||||
public Optional<Pair<ILabel, ILabel>> merge(ILabel a, ILabel b, boolean add) {
|
||||
public Optional<ILabel> merge(ILabel a, ILabel b, boolean add) {
|
||||
Optional<ILabel> ret = functions.get(a.getIdentifier(), b.getIdentifier())
|
||||
.orElse((x, y, f) -> Optional.empty()).merge(a, b, add);
|
||||
if (ret.isPresent() && ret.get() != ILabel.EMPTY && (ret.get() == a || ret.get() == b))
|
||||
throw new RuntimeException("Merger should not modify the given label.");
|
||||
return ret.map(i -> new Pair<>(i, add ? b.copy().invertAmount() : b.copy()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MergerFunction {
|
||||
/**
|
||||
@@ -240,6 +235,11 @@ public interface ILabel {
|
||||
class LEmpty implements ILabel {
|
||||
public static final String IDENTIFIER = "empty";
|
||||
|
||||
@Override
|
||||
public boolean matches(Object l) {
|
||||
return l == this;
|
||||
}
|
||||
|
||||
private LEmpty() {
|
||||
}
|
||||
|
||||
@@ -274,10 +274,10 @@ public interface ILabel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAmount(int amount) {
|
||||
public ILabel setAmount(int amount) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getAmountString() {
|
||||
return "";
|
||||
@@ -309,7 +309,7 @@ public interface ILabel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLabel(JecaGui gui) {
|
||||
public void drawLabel(JecaGui gui, int xPos, int yPos, boolean center) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +317,11 @@ public interface ILabel {
|
||||
public static final String KEY_AMOUNT = "amount";
|
||||
protected int amount;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getDisplayName() + 'x' + getAmount();
|
||||
}
|
||||
|
||||
public Impl(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
@@ -333,6 +338,14 @@ public interface ILabel {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLabel(JecaGui gui, int xPos, int yPos, boolean center) {
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.translate(center ? xPos - 8 : xPos, center ? yPos - 8 : yPos, 0);
|
||||
drawLabel(gui);
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILabel increaseAmount() {
|
||||
setAmount(getAmount() + getMultiplier());
|
||||
@@ -373,7 +386,7 @@ public interface ILabel {
|
||||
@SideOnly(Side.CLIENT)
|
||||
public List<String> getToolTip(List<String> existing, boolean detailed) {
|
||||
if (detailed) existing.add(FORMAT_GREY +
|
||||
Utilities.I18n.format("label.common.tooltip.amount", Integer.toString(getAmount())));
|
||||
Utilities.I18n.format("label.common.tooltip.amount", getAmountString()));
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -390,8 +403,10 @@ public interface ILabel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAmount(int amount) {
|
||||
public ILabel setAmount(int amount) {
|
||||
if (amount == 0) return ILabel.EMPTY;
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -402,7 +417,9 @@ public interface ILabel {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Impl && amount == ((Impl) obj).amount;
|
||||
return obj instanceof Impl && amount == ((Impl) obj).amount && matches(obj);
|
||||
}
|
||||
|
||||
abstract protected void drawLabel(JecaGui gui);
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,14 @@ public class LFluidStack extends ILabel.Impl {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object l) {
|
||||
if (l instanceof LFluidStack) {
|
||||
LFluidStack lfs = (LFluidStack) l;
|
||||
return (nbt == null ? lfs.nbt == null : nbt.equals(lfs.nbt)) && fluid == lfs.fluid;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILabel copy() {
|
||||
return new LFluidStack(this);
|
||||
@@ -88,7 +96,7 @@ public class LFluidStack extends ILabel.Impl {
|
||||
public NBTTagCompound toNBTTagCompound() {
|
||||
NBTTagCompound ret = super.toNBTTagCompound();
|
||||
ret.setString(KEY_FLUID, FluidRegistry.getFluidName(fluid));
|
||||
ret.setTag(KEY_NBT, nbt);
|
||||
if (nbt != null) ret.setTag(KEY_NBT, nbt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -109,13 +117,8 @@ public class LFluidStack extends ILabel.Impl {
|
||||
gui.drawFluid(fluid, 2, 2, 12, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof LFluidStack && fluid.equals(((LFluidStack) obj).fluid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fluid.hashCode();
|
||||
return fluid.getUnlocalizedName().hashCode() ^ amount ^ (nbt == null ? 0 : nbt.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -66,8 +66,7 @@ public class LItemStack extends ILabel.Impl {
|
||||
LItemStack lisA = (LItemStack) a;
|
||||
LItemStack lisB = (LItemStack) b;
|
||||
boolean wildcard = lisA.meta == OreDictionary.WILDCARD_VALUE || lisB.meta == OreDictionary.WILDCARD_VALUE;
|
||||
if ((Objects.equals(lisA.nbt, lisB.nbt)) &&
|
||||
(lisA.meta == lisB.meta || wildcard)) {
|
||||
if ((Objects.equals(lisA.nbt, lisB.nbt)) && (lisA.meta == lisB.meta || wildcard)) {
|
||||
LItemStack ret = new LItemStack(lisA);
|
||||
if (wildcard)
|
||||
ret.meta = OreDictionary.WILDCARD_VALUE;
|
||||
@@ -101,6 +100,15 @@ public class LItemStack extends ILabel.Impl {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object l) {
|
||||
if (l instanceof LItemStack) {
|
||||
LItemStack lis = (LItemStack) l;
|
||||
return (Objects.equals(nbt, lis.nbt)) && meta == lis.meta && item == lis.item;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILabel copy() {
|
||||
return new LItemStack(this);
|
||||
@@ -125,17 +133,8 @@ public class LItemStack extends ILabel.Impl {
|
||||
gui.drawItemStack(0, 0, temp, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof LItemStack) {
|
||||
LItemStack lis = (LItemStack) obj;
|
||||
return super.equals(obj) && lis.item == item && lis.meta == meta && Objects.equals(nbt, lis.nbt);
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return meta ^ item.getUnlocalizedName().hashCode() ^ (nbt == null ? 0 : nbt.hashCode());
|
||||
return (nbt == null ? 0 : nbt.hashCode()) ^ meta ^ item.getUnlocalizedName().hashCode() ^ amount;
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,11 @@ public class LOreDict extends ILabel.Impl {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object l) {
|
||||
return l instanceof LOreDict && name.equals(((LOreDict) l).name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LOreDict copy() {
|
||||
return new LOreDict(this);
|
||||
@@ -169,12 +174,6 @@ public class LOreDict extends ILabel.Impl {
|
||||
return name.hashCode() ^ amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof LOreDict
|
||||
&& amount == ((LOreDict) obj).amount && name.equals(((LOreDict) obj).name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ public class LString extends ILabel.Impl {
|
||||
return "string";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object l) {
|
||||
return l instanceof LString && name.equals(((LString) l).name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILabel copy() {
|
||||
return new LString(this);
|
||||
@@ -66,12 +71,6 @@ public class LString extends ILabel.Impl {
|
||||
gui.drawResource(Resource.LBL_UNIV_F, 0, 0, (name.hashCode() * 0x131723) & 0xFFFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof LString
|
||||
&& name.equals(((LString) obj).name) && amount == ((LString) obj).amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode() ^ amount;
|
||||
|
||||
@@ -105,6 +105,10 @@ public class Recipe {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public boolean matches(ILabel label, enumIoType type) {
|
||||
return Arrays.stream(getLabel(type)).anyMatch(i -> ILabel.MERGER.merge(label, i, true).isPresent());
|
||||
}
|
||||
|
||||
public enum enumIoType {
|
||||
INPUT, OUTPUT, CATALYST;
|
||||
|
||||
|
||||
@@ -7,10 +7,7 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -98,13 +95,8 @@ public class Recipes {
|
||||
return flatStream(group).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public List<Triple<Recipe, String, Integer>> getRecipes(ILabel label, Recipe.enumIoType type) {
|
||||
return getRecipes(flatStream(), r -> IntStream.range(0, type.getSize()).anyMatch(i ->
|
||||
ILabel.MERGER.merge(label, r.getLabel(type)[i], true).isPresent()));
|
||||
}
|
||||
|
||||
private List<Triple<Recipe, String, Integer>> getRecipes(Stream<Triple<Recipe, String, Integer>> list, Predicate<Recipe> pre) {
|
||||
return list.filter(i -> pre.test(i.one)).collect(Collectors.toCollection(ArrayList::new));
|
||||
public Optional<Recipe> getRecipe(ILabel label, Recipe.enumIoType type) {
|
||||
return flatStream().map(i -> i.one).filter(i -> i.matches(label, type)).findFirst();
|
||||
}
|
||||
|
||||
public void forEach(BiConsumer<String, List<Recipe>> consumer) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.towdium.jecalculation.event;
|
||||
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.event.handlers.InputEventHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -7,14 +7,13 @@ import cpw.mods.fml.common.gameevent.PlayerEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import me.towdium.jecalculation.JustEnoughCalculation;
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
import me.towdium.jecalculation.gui.JecaGui;
|
||||
import me.towdium.jecalculation.gui.guis.GuiCalculator;
|
||||
import me.towdium.jecalculation.network.ClientHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraftforge.client.event.GuiScreenEvent;
|
||||
import net.minecraftforge.event.entity.EntityEvent;
|
||||
|
||||
/**
|
||||
* Author: towdium
|
||||
@@ -45,7 +44,7 @@ public class InputEventHandler {
|
||||
@SubscribeEvent
|
||||
public void onLogout(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
JustEnoughCalculation.logger.info("on log out event");
|
||||
ControllerClient.writeToLocal();;
|
||||
Controller.writeToLocal();;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ public class JecaGui extends GuiContainer {
|
||||
root.onDraw(this, mouseX - guiLeft, mouseY - guiTop);
|
||||
GlStateManager.popMatrix();
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.translate(mouseX - 8, mouseY - 8, 80);
|
||||
hand.drawLabel(this);
|
||||
GlStateManager.translate(0, 0, 80);
|
||||
hand.drawLabel(this, mouseX, mouseY, true);
|
||||
GlStateManager.popMatrix();
|
||||
drawBufferedTooltip();
|
||||
GlStateManager.enableLighting();
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.List;
|
||||
@ParametersAreNonnullByDefault
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class WContainer implements IWidget {
|
||||
boolean modified = false;
|
||||
protected List<IWidget> widgets = new ArrayList<>();
|
||||
|
||||
public void add(IWidget w) {
|
||||
@@ -62,13 +63,4 @@ public class WContainer implements IWidget {
|
||||
for (IWidget w : widgets) if (w.onScroll(gui, xMouse, yMouse, diff)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*public Optional<WLabel> getLabelAt(int xMouse, int yMouse) {
|
||||
return drawables.stream().map(w -> {
|
||||
if (w instanceof WLabelGroup) return ((WLabelGroup) w).getLabelAt(xMouse, yMouse);
|
||||
else if (w instanceof WLabel) return Optional.ofNullable(
|
||||
((WLabel) w).mouseIn(xMouse, yMouse) ? ((WLabel) w) : null);
|
||||
else return Optional.<WLabel>empty();
|
||||
}).filter(Optional::isPresent).findFirst().orElse(Optional.empty());
|
||||
}*/
|
||||
}
|
||||
@@ -49,8 +49,8 @@ public class WLabel implements IWidget {
|
||||
|
||||
public void setLabel(ILabel label) {
|
||||
this.label = label;
|
||||
if (mode == enumMode.EDITOR || mode == enumMode.SELECTOR)
|
||||
notifyLsnr();
|
||||
// if (mode == enumMode.EDITOR || mode == enumMode.SELECTOR)
|
||||
// notifyLsnr();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +58,7 @@ public class WLabel implements IWidget {
|
||||
gui.drawResourceContinuous(Resource.WGT_SLOT, xPos, yPos, xSize, ySize, 3, 3, 3, 3);
|
||||
label.drawLabel(gui, xPos + xSize / 2, yPos + ySize / 2, true);
|
||||
if (mode == enumMode.RESULT || mode == enumMode.EDITOR)
|
||||
gui.drawText(xPos + xSize / 2 + 7.5f, yPos + ySize / 2 + 7 -
|
||||
gui.drawText(xPos + xSize / 2.0f + 7.5f, yPos + ySize / 2 + 7 -
|
||||
(int) (font.size * gui.getFontRenderer().FONT_HEIGHT), font, label.getAmountString());
|
||||
if (mouseIn(xMouse, yMouse)) {
|
||||
gui.drawRectangle(xPos + 1, yPos + 1, xSize - 2, ySize - 2, 0x80FFFFFF);
|
||||
|
||||
@@ -55,11 +55,9 @@ public class WLabelScroll extends WContainer {
|
||||
public boolean onScroll(JecaGui gui, int xMouse, int yMouse, int diff) {
|
||||
boolean in = JecaGui.mouseIn(xPos, yPos, column * 18, row * 18, xMouse, yMouse);
|
||||
if (in) {
|
||||
scroll.setLsnrScroll(null);
|
||||
float pos = getPos(current - diff);
|
||||
scroll.setCurrent(pos);
|
||||
update(pos);
|
||||
scroll.setLsnrScroll(this::update);
|
||||
}
|
||||
return in;
|
||||
}
|
||||
@@ -79,6 +77,7 @@ public class WLabelScroll extends WContainer {
|
||||
filtered = labels.stream().filter(l -> Utilities.contains(l.getDisplayName().toLowerCase(), str.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
scroll.setCurrent(0);
|
||||
update(0);
|
||||
return filtered.size() != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class WScroll implements IWidget {
|
||||
|
||||
@Override
|
||||
public void onDraw(JecaGui gui, int xMouse, int yMouse) {
|
||||
if (Mouse.isButtonDown(0) && drag) setCurrent(yMouse - yPos - 9);
|
||||
if (Mouse.isButtonDown(0) && drag) setCurrent(yMouse - yPos - 9, true);
|
||||
else drag = false;
|
||||
|
||||
gui.drawResourceContinuous(Resource.WGT_SLOT, xPos, yPos, 14, ySize, 3, 3, 3, 3);
|
||||
@@ -41,19 +41,19 @@ public class WScroll implements IWidget {
|
||||
@Override
|
||||
public boolean onClicked(JecaGui gui, int xMouse, int yMouse, int button) {
|
||||
drag = mouseIn(xMouse, yMouse);
|
||||
if (drag) setCurrent(yMouse - yPos - 9);
|
||||
if (drag) setCurrent(yMouse - yPos - 9, true);
|
||||
return drag;
|
||||
}
|
||||
|
||||
private void setCurrent(int pos) {
|
||||
private void setCurrent(int pos, boolean notify) {
|
||||
current = pos;
|
||||
if (current < 0) current = 0;
|
||||
if (current > ySize - 17) current = ySize - 17;
|
||||
if (lsnrScroll != null) lsnrScroll.accept(current / (ySize - 17f));
|
||||
if (notify && lsnrScroll != null) lsnrScroll.accept(current / (ySize - 17f));
|
||||
}
|
||||
|
||||
public void setCurrent(float ratio) {
|
||||
setCurrent((int) ((ySize - 17) * ratio));
|
||||
setCurrent((int) ((ySize - 17) * ratio), false);
|
||||
}
|
||||
|
||||
public boolean mouseIn(int xMouse, int yMouse) {
|
||||
|
||||
@@ -22,13 +22,16 @@ public class WSearch extends WContainer {
|
||||
public WSearch(Consumer<ILabel> callback, WTextField tf, WLabelScroll... lss) {
|
||||
clbk = callback;
|
||||
List<WLabelScroll> lst = Arrays.asList(lss);
|
||||
tf.setLsnrText(s -> tf.setColor(lst.stream().anyMatch(ls -> ls.setFilter(s)) ?
|
||||
JecaGui.COLOR_TEXT_WHITE : JecaGui.COLOR_TEXT_RED));
|
||||
tf.setLsnrText(s -> {
|
||||
boolean b = false;
|
||||
for (WLabelScroll i : lss) if (i.setFilter(s)) b = true;
|
||||
tf.setColor(b ? JecaGui.COLOR_TEXT_WHITE : JecaGui.COLOR_TEXT_RED);
|
||||
});
|
||||
add(tf);
|
||||
addAll(lss);
|
||||
for (WLabelScroll i : lss)
|
||||
i.setLsnrUpdate(j -> {
|
||||
if (clbk != null) clbk.accept(i.getLabelAt(j));
|
||||
if (clbk != null) clbk.accept(i.getLabelAt(j).copy());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,8 @@ public class WTextField implements IWidget {
|
||||
|
||||
@Override
|
||||
public boolean onClicked(JecaGui gui, int xMouse, int yMouse, int button) {
|
||||
boolean flag = xMouse >= textField.xPosition && xMouse < textField.xPosition + textField.width &&
|
||||
yMouse >= textField.yPosition && yMouse < textField.yPosition + textField.height;
|
||||
textField.mouseClicked(xMouse, yMouse, button);
|
||||
return textField.isFocused() && flag && button == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@ import me.towdium.jecalculation.gui.JecaGui;
|
||||
import me.towdium.jecalculation.gui.Resource;
|
||||
import me.towdium.jecalculation.gui.drawables.*;
|
||||
import me.towdium.jecalculation.data.label.ILabel;
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.List;
|
||||
@@ -40,17 +40,17 @@ public class GuiCalculator extends WContainer {
|
||||
add(new WIcon(151, 31, 18, 18, Resource.ICN_RECENT_N, Resource.ICN_RECENT_F, "calculator.history"));
|
||||
add(new WSwitcher(7, 56, 162, 5));
|
||||
refresh();
|
||||
List<ILabel> recent = ControllerClient.getRecent();
|
||||
if (recent.size() > 0) wLabel.setLabel(ControllerClient.getRecent().get(0));
|
||||
List<ILabel> recent = Controller.getRecent();
|
||||
if (recent.size() > 0) wLabel.setLabel(Controller.getRecent().get(0));
|
||||
wLabel.setLsnrUpdate(() -> {
|
||||
ControllerClient.setRecent(wLabel.label);
|
||||
Controller.setRecent(wLabel.label);
|
||||
refresh();
|
||||
});
|
||||
wRecent.setLsnrUpdate(l -> JecaGui.getCurrent().hand = wRecent.getLabelAt(l));
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
List<ILabel> recent = ControllerClient.getRecent();
|
||||
List<ILabel> recent = Controller.getRecent();
|
||||
if (recent.size() > 1)
|
||||
wRecent.setLabel(recent.subList(1, recent.size()), 0);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package me.towdium.jecalculation.gui.guis;
|
||||
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
import me.towdium.jecalculation.algorithm.CostList;
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
import me.towdium.jecalculation.data.label.ILabel;
|
||||
import me.towdium.jecalculation.data.structure.Recipe;
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.gui.JecaGui;
|
||||
import me.towdium.jecalculation.gui.Resource;
|
||||
import me.towdium.jecalculation.gui.drawables.*;
|
||||
@@ -26,11 +26,11 @@ import java.util.stream.Stream;
|
||||
public class GuiRecipe extends WContainer {
|
||||
Pair<String, Integer> dest;
|
||||
HashMap<Integer, List<ILabel>> disambiguation = new HashMap<>();
|
||||
WSwitcher switcherGroup = new WSwitcher(7, 7, 162, ControllerClient.getGroups());
|
||||
WSwitcher switcherGroup = new WSwitcher(7, 7, 162, Controller.getGroups());
|
||||
WTextField textField = new WTextField(49, 33, 119);
|
||||
WButton buttonCopy = new WButtonIcon(83, 33, 20, 20, Resource.BTN_COPY_N, Resource.BTN_COPY_F,
|
||||
Resource.BTN_COPY_D, "recipe.copy").setDisabled(true).setListenerLeft(() -> {
|
||||
ControllerClient.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
Controller.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
JecaGui.displayParent();
|
||||
});
|
||||
|
||||
@@ -61,13 +61,13 @@ public class GuiRecipe extends WContainer {
|
||||
WButton buttonSave = new WButtonIcon(26, 33, 20, 20, Resource.BTN_SAVE_N, Resource.BTN_SAVE_F, "recipe.save")
|
||||
.setListenerLeft(() -> {
|
||||
if (dest == null)
|
||||
ControllerClient.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
Controller.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
else {
|
||||
if (textField.getText().equals(dest.one))
|
||||
ControllerClient.setRecipe(dest.one, dest.two, toRecipe());
|
||||
Controller.setRecipe(dest.one, dest.two, toRecipe());
|
||||
else {
|
||||
ControllerClient.removeRecipe(dest.one, dest.two);
|
||||
ControllerClient.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
Controller.removeRecipe(dest.one, dest.two);
|
||||
Controller.addRecipe(switcherGroup.getText(), toRecipe());
|
||||
}
|
||||
}
|
||||
JecaGui.displayParent();
|
||||
@@ -77,7 +77,7 @@ public class GuiRecipe extends WContainer {
|
||||
.setListenerLeft(() -> setModeNewGroup(true));
|
||||
WButton buttonDel = new WButtonIcon(102, 33, 20, 20, Resource.BTN_NO_N, Resource.BTN_NO_F, Resource.BTN_NO_D,
|
||||
"recipe.delete").setDisabled(true).setListenerLeft(() -> {
|
||||
ControllerClient.removeRecipe(dest.one, dest.two);
|
||||
Controller.removeRecipe(dest.one, dest.two);
|
||||
JecaGui.displayParent();
|
||||
});
|
||||
WButton buttonYes = new WButtonIcon(7, 33, 20, 20, Resource.BTN_YES_N, Resource.BTN_YES_F, "recipe.confirm")
|
||||
@@ -93,9 +93,9 @@ public class GuiRecipe extends WContainer {
|
||||
public GuiRecipe(String group, int index) {
|
||||
this();
|
||||
dest = new Pair<>(group, index);
|
||||
Recipe r = ControllerClient.getRecipe(group, index);
|
||||
Recipe r = Controller.getRecipe(group, index);
|
||||
fromRecipe(r);
|
||||
switcherGroup.setIndex(ControllerClient.getGroups().indexOf(group));
|
||||
switcherGroup.setIndex(Controller.getGroups().indexOf(group));
|
||||
buttonCopy.setDisabled(false);
|
||||
buttonDel.setDisabled(false);
|
||||
}
|
||||
@@ -131,56 +131,51 @@ public class GuiRecipe extends WContainer {
|
||||
// item disamb raw
|
||||
ArrayList<Triple<ILabel, CostList, CostList>> input = new ArrayList<>();
|
||||
ArrayList<Triple<ILabel, CostList, CostList>> output = new ArrayList<>();
|
||||
HashMap<Integer, List<ILabel>> disamb = new HashMap<>();
|
||||
|
||||
BiConsumer<ArrayList<Triple<ILabel, CostList, CostList>>, List<ILabel>> merge = (dst, list) -> {
|
||||
if (list.isEmpty())
|
||||
return;
|
||||
dst.stream().filter(p -> {
|
||||
CostList cl = new CostList(list);
|
||||
if (p.three.equals(cl)) {
|
||||
ILabel.MERGER.merge(p.one, ILabel.CONVERTER.first(list), true).ifPresent(i -> p.one = i.one);
|
||||
p.two.merge(cl, true);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}).findAny().orElseGet(() -> {
|
||||
Triple<ILabel, CostList, CostList> ret = new Triple<>(ILabel.CONVERTER.first(list), new CostList(list),
|
||||
new CostList(list));
|
||||
dst.add(ret);
|
||||
return ret;
|
||||
});
|
||||
};
|
||||
|
||||
BiFunction<ArrayList<Triple<ILabel, CostList, CostList>>, Integer, ArrayList<ILabel>> sort = (src, offset) -> {
|
||||
ArrayList<ILabel> ret = new ArrayList<>();
|
||||
for (int i = 0; i < src.size(); i++) {
|
||||
Triple<ILabel, CostList, CostList> p = src.get(i);
|
||||
ret.add(p.one);
|
||||
if (!ILabel.CONVERTER.guess(p.three.getLabels()).isEmpty())
|
||||
disamb.put(i + offset, p.two.getLabels());
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
disambiguation = new HashMap<>();
|
||||
|
||||
// input
|
||||
List<ItemStack> itemStacks = recipe.getIngredientStacks(recipeIndex).stream()
|
||||
.map((positionedStack) -> positionedStack.item).collect(Collectors.toList());
|
||||
|
||||
|
||||
merge.accept(input, itemStacks.stream().map(ILabel.Converter::from).collect(Collectors.toList()));
|
||||
merge(input, itemStacks.stream().map(ILabel.Converter::from).collect(Collectors.toList()));
|
||||
|
||||
// output
|
||||
ItemStack outputStack = recipe.getResultStack(recipeIndex).item;
|
||||
merge.accept(output, Stream.of(outputStack).map(ILabel.Converter::from).collect(Collectors.toList()));
|
||||
merge(output, Stream.of(outputStack).map(ILabel.Converter::from).collect(Collectors.toList()));
|
||||
|
||||
groupInput.setLabel(sort.apply(input, 0), 0);
|
||||
groupOutput.setLabel(sort.apply(output, 14), 0);
|
||||
disambiguation = disamb;
|
||||
groupInput.setLabel(sort(input, 0, true), 0);
|
||||
groupOutput.setLabel(sort(output, 14, false), 0);
|
||||
refresh();
|
||||
}
|
||||
|
||||
Recipe toRecipe() {
|
||||
private void merge(ArrayList<Triple<ILabel, CostList, CostList>> dst, List<ILabel> list) {
|
||||
if (list.isEmpty()) return;
|
||||
dst.stream().filter(p -> {
|
||||
CostList cl = new CostList(list);
|
||||
if (p.three.equals(cl)) {
|
||||
ILabel.MERGER.merge(p.one, ILabel.CONVERTER.first(list), true).ifPresent(i -> p.one = i);
|
||||
p.two = p.two.merge(cl, true, true);
|
||||
return true;
|
||||
} else return false;
|
||||
}).findAny().orElseGet(() -> {
|
||||
Triple<ILabel, CostList, CostList> ret = new Triple<>(
|
||||
ILabel.CONVERTER.first(list), new CostList(list), new CostList(list));
|
||||
dst.add(ret);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
private ArrayList<ILabel> sort(ArrayList<Triple<ILabel, CostList, CostList>> src, int offset, boolean guess) {
|
||||
ArrayList<ILabel> ret = new ArrayList<>();
|
||||
for (int i = 0; i < src.size(); i++) {
|
||||
Triple<ILabel, CostList, CostList> p = src.get(i);
|
||||
ret.add(p.one);
|
||||
if (guess && !ILabel.CONVERTER.guess(p.three.getLabels()).isEmpty())
|
||||
disambiguation.put(i + offset, p.two.getLabels());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Recipe toRecipe() {
|
||||
return new Recipe(groupInput.getLabels(), groupCatalyst.getLabels(), groupOutput.getLabels());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.towdium.jecalculation.gui.guis;
|
||||
|
||||
import me.towdium.jecalculation.data.structure.Recipe;
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
import me.towdium.jecalculation.gui.JecaGui;
|
||||
import me.towdium.jecalculation.gui.Resource;
|
||||
import me.towdium.jecalculation.gui.drawables.*;
|
||||
@@ -20,7 +20,7 @@ public class GuiSearch extends WContainer {
|
||||
public GuiSearch() {
|
||||
ArrayList<String> groups = new ArrayList<>();
|
||||
groups.add(Utilities.I18n.format("gui.search.all"));
|
||||
groups.addAll(ControllerClient.getGroups());
|
||||
groups.addAll(Controller.getGroups());
|
||||
switcherGroup = new WSwitcher(7, 7, 162, groups).setListener(this::refresh);
|
||||
labelScroll = new WLabelScroll(7, 51, 8, 6, WLabel.enumMode.PICKER, true);
|
||||
add(new WPanel());
|
||||
@@ -34,7 +34,7 @@ public class GuiSearch extends WContainer {
|
||||
|
||||
public void refresh() {
|
||||
int iGroup = switcherGroup.getIndex();
|
||||
content = iGroup == 0 ? ControllerClient.getRecipes() : ControllerClient.getRecipes(switcherGroup.getText());
|
||||
content = iGroup == 0 ? Controller.getRecipes() : Controller.getRecipes(switcherGroup.getText());
|
||||
labelScroll.setLabels(content.stream().map(i -> i.one.getRep()).collect(Collectors.toCollection(ArrayList::new)));
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,14 @@ package me.towdium.jecalculation.network;
|
||||
|
||||
import cpw.mods.fml.client.registry.ClientRegistry;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import me.towdium.jecalculation.data.ControllerClient;
|
||||
import me.towdium.jecalculation.data.Controller;
|
||||
import me.towdium.jecalculation.event.Handlers;
|
||||
import me.towdium.jecalculation.gui.JecaGui;
|
||||
import me.towdium.jecalculation.gui.guis.GuiCalculator;
|
||||
import me.towdium.jecalculation.command.JecaCommand;
|
||||
import me.towdium.jecalculation.data.label.ILabel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.settings.KeyBinding;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraftforge.client.ClientCommandHandler;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public class ClientHandler {
|
||||
@@ -27,7 +24,7 @@ public class ClientHandler {
|
||||
ILabel.initServer();
|
||||
ClientCommandHandler.instance.registerCommand(new JecaCommand());
|
||||
ILabel.initClient();
|
||||
ControllerClient.loadFromLocal();
|
||||
Controller.loadFromLocal();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
|
||||
Reference in new issue
Block a user