aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/utils')
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Constants.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ItemUtils.java168
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java82
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java29
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java210
15 files changed, 382 insertions, 213 deletions
diff --git a/src/main/java/de/hysky/skyblocker/utils/Constants.java b/src/main/java/de/hysky/skyblocker/utils/Constants.java
index 403689ac..d900f917 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Constants.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Constants.java
@@ -12,7 +12,7 @@ import java.util.function.Supplier;
* Holds generic static constants
*/
public interface Constants {
- String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u0FC7\uA925\uA56A\u12DE";
+ String LEVEL_EMBLEMS = "\u2E15\u273F\u2741\u2E19\u03B1\u270E\u2615\u2616\u2663\u213B\u2694\u27B6\u26A1\u2604\u269A\u2693\u2620\u269B\u2666\u2660\u2764\u2727\u238A\u1360\u262C\u269D\u29C9\uA214\u32D6\u2E0E\u26A0\uA541\u3020\u30C4\u2948\u2622\u2623\u273E\u269C\u0BD0\u0A6D\u2742\u16C3\u3023\u10F6\u0444\u266A\u266B\u04C3\u26C1\u26C3\u16DD\uA03E\u1C6A\u03A3\u09EB\u2603\u2654\u26C2\u0FC7\uA925\uA56A\u2592\u12DE";
Supplier<MutableText> PREFIX = () -> {
LocalDate time = LocalDate.now();
return Text.empty()
diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
index 70a8c241..086686a7 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
@@ -1,29 +1,41 @@
package de.hysky.skyblocker.utils;
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+import com.mojang.authlib.properties.Property;
+import com.mojang.authlib.properties.PropertyMap;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
-import com.mojang.brigadier.exceptions.CommandSyntaxException;
-import de.hysky.skyblocker.mixin.accessor.ItemStackAccessor;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.minecraft.component.ComponentChanges;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.component.type.NbtComponent;
+import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
-import net.minecraft.nbt.StringNbtReader;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.Text;
-import net.minecraft.text.Texts;
import net.minecraft.util.Formatting;
+import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.time.temporal.TemporalAccessor;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -31,83 +43,72 @@ import java.util.regex.Pattern;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
public class ItemUtils {
- private static final Logger LOGGER = LoggerFactory.getLogger(ItemUtils.class);
- public static final String EXTRA_ATTRIBUTES = "ExtraAttributes";
public static final String ID = "id";
public static final String UUID = "uuid";
private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH);
- private static final SimpleDateFormat OLD_OBTAINED_DATE_FORMAT = new SimpleDateFormat("MM/dd/yy");
+ private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH);
public static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]");
public static final Predicate<String> FUEL_PREDICATE = line -> line.contains("Fuel: ");
-
- public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemNbtCommand() {
- return literal("dumpHeldItemNbt").executes(context -> {
- context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item Nbt: " + context.getSource().getPlayer().getMainHandStack().writeNbt(new NbtCompound())));
+ private static final Gson GSON = new Gson(); //GSON Instance with no config
+ private static final Codec<RegistryEntry<Item>> EMPTY_ALLOWING_ITEM_CODEC = Registries.ITEM.getEntryCodec();
+ public static final Codec<ItemStack> EMPTY_ALLOWING_ITEMSTACK_CODEC = Codec.lazyInitialized(() -> RecordCodecBuilder.create(instance -> instance.group(
+ EMPTY_ALLOWING_ITEM_CODEC.fieldOf("id").forGetter(ItemStack::getRegistryEntry),
+ Codec.INT.orElse(1).fieldOf("count").forGetter(ItemStack::getCount),
+ ComponentChanges.CODEC.optionalFieldOf("components", ComponentChanges.EMPTY).forGetter(ItemStack::getComponentChanges)
+ ).apply(instance, ItemStack::new)));
+
+ public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemCommand() {
+ return literal("dumpHeldItem").executes(context -> {
+ context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item: " + GSON.toJson(ItemStack.CODEC.encodeStart(JsonOps.INSTANCE, context.getSource().getPlayer().getMainHandStack()).getOrThrow())));
return Command.SINGLE_SUCCESS;
});
}
- /**
- * Gets the {@code ExtraAttributes} NBT tag from the item stack.
- *
- * @param stack the item stack to get the {@code ExtraAttributes} NBT tag from
- * @return an optional containing the {@code ExtraAttributes} NBT tag of the item stack
- */
- public static Optional<NbtCompound> getExtraAttributesOptional(@NotNull ItemStack stack) {
- return Optional.ofNullable(stack.getSubNbt(EXTRA_ATTRIBUTES));
- }
-
- /**
- * Gets the {@code ExtraAttributes} NBT tag from the item stack.
- *
- * @param stack the item stack to get the {@code ExtraAttributes} NBT tag from
- * @return the {@code ExtraAttributes} NBT tag of the item stack, or null if the item stack is null or does not have an {@code ExtraAttributes} NBT tag
- */
- @Nullable
- public static NbtCompound getExtraAttributes(@NotNull ItemStack stack) {
- return stack.getSubNbt(EXTRA_ATTRIBUTES);
+ @SuppressWarnings("deprecation")
+ public static NbtCompound getCustomData(@NotNull ItemStack stack) {
+ return stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).getNbt();
}
/**
- * Gets the internal name of the item stack from the {@code ExtraAttributes} NBT tag.
+ * Gets the Skyblock item id of the item stack.
*
* @param stack the item stack to get the internal name from
* @return an optional containing the internal name of the item stack
*/
- public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) {
- return getExtraAttributesOptional(stack).map(extraAttributes -> extraAttributes.getString(ID));
+ public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) {
+ NbtCompound customData = getCustomData(stack);
+ return customData.contains(ID) ? Optional.of(customData.getString(ID)) : Optional.empty();
}
/**
- * Gets the internal name of the item stack from the {@code ExtraAttributes} NBT tag.
+ * Gets the Skyblock item id of the item stack.
*
* @param stack the item stack to get the internal name from
* @return the internal name of the item stack, or an empty string if the item stack is null or does not have an internal name
*/
- public static String getItemId(@NotNull ItemStack stack) {
- NbtCompound extraAttributes = getExtraAttributes(stack);
- return extraAttributes != null ? extraAttributes.getString(ID) : "";
+ public static String getItemId(@NotNull ItemStack stack) {
+ return getCustomData(stack).getString(ID);
}
/**
- * Gets the UUID of the item stack from the {@code ExtraAttributes} NBT tag.
+ * Gets the UUID of the item stack.
*
* @param stack the item stack to get the UUID from
* @return an optional containing the UUID of the item stack
*/
- public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) {
- return getExtraAttributesOptional(stack).map(extraAttributes -> extraAttributes.getString(UUID));
+ public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) {
+ NbtCompound customData = getCustomData(stack);
+ return customData.contains(UUID) ? Optional.of(customData.getString(UUID)) : Optional.empty();
}
/**
- * Gets the UUID of the item stack from the {@code ExtraAttributes} NBT tag.
+ * Gets the UUID of the item stack.
*
* @param stack the item stack to get the UUID from
* @return the UUID of the item stack, or an empty string if the item stack is null or does not have a UUID
*/
- public static String getItemUuid(@NotNull ItemStack stack) {
- NbtCompound extraAttributes = getExtraAttributes(stack);
- return extraAttributes != null ? extraAttributes.getString(UUID) : "";
+ public static String getItemUuid(@NotNull ItemStack stack) {
+ return getCustomData(stack).getString(UUID);
}
/**
@@ -126,47 +127,41 @@ public class ItemUtils {
* @param stack the item under the pointer
* @return if the item have a "Timestamp" it will be shown formated on the tooltip
*/
- public static String getTimestamp(ItemStack stack) {
- NbtCompound ea = getExtraAttributes(stack);
-
- if (ea != null && ea.contains("timestamp", NbtElement.LONG_TYPE)) {
- Instant date = Instant.ofEpochMilli(ea.getLong("timestamp"));
+ public static String getTimestamp(ItemStack stack) {
+ NbtCompound customData = getCustomData(stack);
+ if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) {
+ Instant date = Instant.ofEpochMilli(customData.getLong("timestamp"));
return OBTAINED_DATE_FORMATTER.format(date);
}
- if (ea != null && ea.contains("timestamp", NbtElement.STRING_TYPE)) {
- try {
- Instant date = OLD_OBTAINED_DATE_FORMAT.parse(ea.getString("timestamp")).toInstant();
-
- return OBTAINED_DATE_FORMATTER.format(date);
- } catch (ParseException e) {
- LOGGER.warn("[Skyblocker Item Utils] Encountered an unknown exception while parsing time stamp of item {} with extra attributes {}", stack, ea, e);
- }
+ if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) {
+ TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp"));
+ return OBTAINED_DATE_FORMATTER.format(date);
}
return "";
}
public static boolean hasCustomDurability(@NotNull ItemStack stack) {
- NbtCompound extraAttributes = getExtraAttributes(stack);
- return extraAttributes != null && (extraAttributes.contains("drill_fuel") || extraAttributes.getString(ID).equals("PICKONIMBUS"));
+ NbtCompound customData = getCustomData(stack);
+ return customData != null && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS"));
}
@Nullable
public static IntIntPair getDurability(@NotNull ItemStack stack) {
- NbtCompound extraAttributes = getExtraAttributes(stack);
- if (extraAttributes == null) return null;
+ NbtCompound customData = getCustomData(stack);
+ if (customData == null) return null;
// TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level
// TODO Cache the max durability and only update the current durability on inventory tick
- int pickonimbusDurability = extraAttributes.getInt("pickonimbus_durability");
+ int pickonimbusDurability = customData.getInt("pickonimbus_durability");
if (pickonimbusDurability > 0) {
return IntIntPair.of(pickonimbusDurability, 5000);
}
- String drillFuel = Formatting.strip(getNbtTooltip(stack, FUEL_PREDICATE));
+ String drillFuel = Formatting.strip(getLoreLineIf(stack, FUEL_PREDICATE));
if (drillFuel != null) {
String[] drillFuelStrings = NOT_DURABILITY.matcher(drillFuel).replaceAll("").trim().split("/");
return IntIntPair.of(Integer.parseInt(drillFuelStrings[0]), Integer.parseInt(drillFuelStrings[1]) * 1000);
@@ -176,8 +171,8 @@ public class ItemUtils {
}
@Nullable
- public static String getNbtTooltip(ItemStack item, Predicate<String> predicate) {
- for (Text line : getNbtTooltips(item)) {
+ public static String getLoreLineIf(ItemStack item, Predicate<String> predicate) {
+ for (Text line : getLore(item)) {
String string = line.getString();
if (predicate.test(string)) {
return string;
@@ -188,8 +183,8 @@ public class ItemUtils {
}
@Nullable
- public static Matcher getNbtTooltip(ItemStack item, Pattern pattern) {
- for (Text line : getNbtTooltips(item)) {
+ public static Matcher getLoreLineIfMatch(ItemStack item, Pattern pattern) {
+ for (Text line : getLore(item)) {
String string = line.getString();
Matcher matcher = pattern.matcher(string);
if (matcher.matches()) {
@@ -200,19 +195,32 @@ public class ItemUtils {
return null;
}
- public static List<Text> getNbtTooltips(ItemStack item) {
- NbtCompound displayNbt = item.getSubNbt("display");
- if (displayNbt == null || !displayNbt.contains("Lore", NbtElement.LIST_TYPE)) {
- return Collections.emptyList();
- }
+ public static List<Text> getLore(ItemStack item) {
+ return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines();
+ }
+
+ public static PropertyMap propertyMapWithTexture(String textureValue) {
+ return Codecs.GAME_PROFILE_PROPERTY_MAP.parse(JsonOps.INSTANCE, JsonParser.parseString("[{\"name\":\"textures\",\"value\":\"" + textureValue + "\"}]")).getOrThrow();
+ }
+
+ public static String getHeadTexture(ItemStack stack) {
+ if (!stack.isOf(Items.PLAYER_HEAD) || !stack.contains(DataComponentTypes.PROFILE)) return "";
+
+ ProfileComponent profile = stack.get(DataComponentTypes.PROFILE);
+ String texture = profile.properties().get("textures").stream()
+ .map(Property::value)
+ .findFirst()
+ .orElse("");
- return displayNbt.getList("Lore", NbtElement.STRING_TYPE).stream().map(NbtElement::asString).map(Text.Serialization::fromJson).filter(Objects::nonNull).map(text -> Texts.setStyleIfAbsent(text, ItemStackAccessor.getLORE_STYLE())).map(Text.class::cast).toList();
+ return texture;
}
public static ItemStack getSkyblockerStack() {
try {
- return ItemStack.fromNbt(StringNbtReader.parse("{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;-300151517,-631415889,-1193921967,-1821784279],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=\"}]}}}}"));
- } catch (CommandSyntaxException e) {
+ ItemStack stack = new ItemStack(Items.PLAYER_HEAD);
+ stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of("SkyblockerStack"), Optional.of(java.util.UUID.randomUUID()), propertyMapWithTexture("e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=")));
+ return stack;
+ } catch (Exception e) {
throw new RuntimeException(e);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
index cda92273..f8716ca4 100644
--- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
@@ -51,6 +51,10 @@ public class NEURepoManager {
);
}
+ public static boolean isLoading() {
+ return REPO_LOADING != null && !REPO_LOADING.isDone();
+ }
+
private static CompletableFuture<Boolean> loadRepository() {
return CompletableFuture.supplyAsync(() -> {
try {
@@ -78,7 +82,7 @@ public class NEURepoManager {
}
private static void deleteAndDownloadRepository(PlayerEntity player) {
- if (REPO_LOADING != null && !REPO_LOADING.isDone()) {
+ if (isLoading()) {
sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.loading")));
return;
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index bbee3ce1..dc13b61d 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.utils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -217,7 +219,6 @@ public class Utils {
}
if (sidebar.isEmpty() && !fabricLoader.isDevelopmentEnvironment()) return;
- String string = sidebar.toString();
if (fabricLoader.isDevelopmentEnvironment() || isConnectedToHypixel(client)) {
if (!isOnHypixel) {
@@ -466,4 +467,17 @@ public class Utils {
});
}
+
+ /**
+ * Used to avoid triggering things like chat rules or chat listeners infinitely, do not use otherwise.
+ *
+ * Bypasses MessageHandler#onGameMessage
+ */
+ public static void sendMessageToBypassEvents(Text message) {
+ MinecraftClient client = MinecraftClient.getInstance();
+
+ client.inGameHud.getChatHud().addMessage(message);
+ ((MessageHandlerAccessor) client.getMessageHandler()).invokeAddToChatLog(message, Instant.now());
+ client.getNarratorManager().narrateSystemMessage(message);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
index bdff2d94..1f0caff5 100644
--- a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
+++ b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
@@ -15,6 +15,7 @@ import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
@FunctionalInterface
public interface ChatMessageListener {
@@ -68,7 +69,7 @@ public interface ChatMessageListener {
if (!Utils.isOnSkyblock()) {
return true;
}
- ChatFilterResult result = EVENT.invoker().onMessage(message, message.getString());
+ ChatFilterResult result = EVENT.invoker().onMessage(message, Formatting.strip(message.getString()));
switch (result) {
case ACTION_BAR -> {
if (overlay) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java
new file mode 100644
index 00000000..3543a2f1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java
@@ -0,0 +1,82 @@
+package de.hysky.skyblocker.utils.datafixer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.mojang.brigadier.StringReader;
+import com.mojang.serialization.Dynamic;
+
+import net.minecraft.command.argument.ItemStringReader;
+import net.minecraft.command.argument.ItemStringReader.ItemResult;
+import net.minecraft.component.DataComponentType;
+import net.minecraft.datafixer.Schemas;
+import net.minecraft.datafixer.TypeReferences;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.registry.DynamicRegistryManager;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.RegistryOps;
+import net.minecraft.util.Identifier;
+
+/**
+ * Contains a data fixer to convert legacy item NBT to the new components system, among other fixers related to the item components system.
+ *
+ * @see net.minecraft.datafixer.fix.ItemStackComponentizationFix
+ */
+public class ItemStackComponentizationFixer {
+ private static final int ITEM_NBT_DATA_VERSION = 3817;
+ private static final int ITEM_COMPONENTS_DATA_VERSION = 3825;
+ private static final DynamicRegistryManager REGISTRY_MANAGER = new DynamicRegistryManager.ImmutableImpl(List.of(Registries.ITEM, Registries.DATA_COMPONENT_TYPE));
+
+ public static ItemStack fixUpItem(NbtCompound nbt) {
+ Dynamic<NbtElement> dynamic = Schemas.getFixer().update(TypeReferences.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, nbt), ITEM_NBT_DATA_VERSION, ITEM_COMPONENTS_DATA_VERSION);
+
+ return ItemStack.CODEC.parse(dynamic).getOrThrow();
+ }
+
+ /**
+ * Modified version of {@link net.minecraft.command.argument.ItemStackArgument#asString(net.minecraft.registry.RegistryWrapper.WrapperLookup)} to only care about changed components.
+ *
+ * @return The {@link ItemStack}'s components as a string which is in the format that the {@code /give} command accepts.
+ */
+ public static String componentsAsString(ItemStack stack) {
+ RegistryOps<NbtElement> nbtRegistryOps = REGISTRY_MANAGER.getOps(NbtOps.INSTANCE);
+
+ return Arrays.toString(stack.getComponentChanges().entrySet().stream().map(entry -> {
+ @SuppressWarnings("unchecked")
+ DataComponentType<Object> dataComponentType = (DataComponentType<Object>) entry.getKey();
+ Identifier componentId = Registries.DATA_COMPONENT_TYPE.getId(dataComponentType);
+ Optional<NbtElement> encodedComponent = dataComponentType.getCodec().encodeStart(nbtRegistryOps, entry.getValue().orElseThrow()).result();
+
+ if (componentId == null || encodedComponent.isEmpty()) {
+ return null;
+ }
+
+ return componentId + "=" + encodedComponent.orElseThrow();
+ }).filter(Objects::nonNull).toArray());
+ }
+
+ /**
+ * Constructs an {@link ItemStack} from an {@code itemId}, with item components in string format as returned by {@link #componentsAsString(ItemStack)}, and with a specified stack count.
+ *
+ * @return an {@link ItemStack} or {@link ItemStack#EMPTY} if there was an exception thrown.
+ */
+ public static ItemStack fromComponentsString(String itemId, int count, String componentsString) {
+ ItemStringReader reader = new ItemStringReader(REGISTRY_MANAGER);
+
+ try {
+ ItemResult result = reader.consume(new StringReader(itemId + componentsString));
+ ItemStack stack = new ItemStack(result.item(), count);
+
+ stack.applyComponentsFrom(result.components());
+
+ return stack;
+ } catch (Exception ignored) {}
+
+ return ItemStack.EMPTY;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java b/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java
index 3fe79e43..d82b4497 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/FrustumUtils.java
@@ -1,7 +1,7 @@
package de.hysky.skyblocker.utils.render;
-import de.hysky.skyblocker.mixin.accessor.FrustumInvoker;
-import de.hysky.skyblocker.mixin.accessor.WorldRendererAccessor;
+import de.hysky.skyblocker.mixins.accessors.FrustumInvoker;
+import de.hysky.skyblocker.mixins.accessors.WorldRendererAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Frustum;
import net.minecraft.util.math.Box;
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java
new file mode 100644
index 00000000..220dbf86
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/MatrixHelper.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.utils.render;
+
+import org.joml.Matrix4f;
+
+import net.minecraft.client.util.math.MatrixStack;
+
+/**
+ * Matrix helper methods
+ */
+public interface MatrixHelper {
+
+ /**
+ * Copies the {@code matrix} into a new {@link Matrix4f}. This is necessary otherwise
+ * any transformations applied will affect other uses of the same matrix.
+ */
+ static Matrix4f copyOf(Matrix4f matrix) {
+ return new Matrix4f(matrix);
+ }
+
+ /**
+ * Creates a blank {@link MatrixStack} and sets it's position matrix to the supplied
+ * {@code positionMatrix}.
+ */
+ static MatrixStack toStack(Matrix4f positionMatrix) {
+ MatrixStack matrices = new MatrixStack();
+ matrices.peek().getPositionMatrix().set(positionMatrix);
+
+ return matrices;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
index da179d0e..a6772fb2 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
@@ -3,8 +3,8 @@ package de.hysky.skyblocker.utils.render;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.logging.LogUtils;
import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.mixin.accessor.BeaconBlockEntityRendererInvoker;
-import de.hysky.skyblocker.mixin.accessor.DrawContextInvoker;
+import de.hysky.skyblocker.mixins.accessors.BeaconBlockEntityRendererInvoker;
+import de.hysky.skyblocker.mixins.accessors.DrawContextInvoker;
import de.hysky.skyblocker.utils.render.culling.OcclusionCulling;
import de.hysky.skyblocker.utils.render.title.Title;
import de.hysky.skyblocker.utils.render.title.TitleContainer;
@@ -26,6 +26,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
+
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -243,15 +244,13 @@ public class RenderHelper {
}
public static void renderQuad(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, boolean throughWalls) {
+ Matrix4f positionMatrix = new Matrix4f();
Vec3d camera = context.camera().getPos();
- MatrixStack matrices = context.matrixStack();
- matrices.push();
- matrices.translate(-camera.x, -camera.y, -camera.z);
+ positionMatrix.translate((float) -camera.x, (float) -camera.y, (float) -camera.z);
Tessellator tessellator = RenderSystem.renderThreadTesselator();
BufferBuilder buffer = tessellator.getBuffer();
- Matrix4f positionMatrix = matrices.peek().getPositionMatrix();
RenderSystem.setShader(GameRenderer::getPositionColorProgram);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
@@ -268,8 +267,6 @@ public class RenderHelper {
RenderSystem.enableCull();
RenderSystem.depthFunc(GL11.GL_LEQUAL);
-
- matrices.pop();
}
public static void renderText(WorldRenderContext context, Text text, Vec3d pos, boolean throughWalls) {
@@ -290,19 +287,18 @@ public class RenderHelper {
* @param throughWalls whether the text should be able to be seen through walls or not.
*/
public static void renderText(WorldRenderContext context, OrderedText text, Vec3d pos, float scale, float yOffset, boolean throughWalls) {
- MatrixStack matrices = context.matrixStack();
- Vec3d camera = context.camera().getPos();
+ Matrix4f positionMatrix = new Matrix4f();
+ Camera camera = context.camera();
+ Vec3d cameraPos = camera.getPos();
TextRenderer textRenderer = client.textRenderer;
scale *= 0.025f;
- matrices.push();
- matrices.translate(pos.getX() - camera.getX(), pos.getY() - camera.getY(), pos.getZ() - camera.getZ());
- matrices.peek().getPositionMatrix().mul(RenderSystem.getModelViewMatrix());
- matrices.multiply(context.camera().getRotation());
- matrices.scale(-scale, -scale, scale);
+ positionMatrix
+ .translate((float) (pos.getX() - cameraPos.getX()), (float) (pos.getY() - cameraPos.getY()), (float) (pos.getZ() - cameraPos.getZ()))
+ .rotate(camera.getRotation())
+ .scale(-scale, -scale, scale);
- Matrix4f positionMatrix = matrices.peek().getPositionMatrix();
float xOffset = -textRenderer.getWidth(text) / 2f;
Tessellator tessellator = RenderSystem.renderThreadTesselator();
@@ -315,7 +311,6 @@ public class RenderHelper {
consumers.draw();
RenderSystem.depthFunc(GL11.GL_LEQUAL);
- matrices.pop();
}
/**
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
index 4f648b8c..ef2e6bf9 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
@@ -1,6 +1,6 @@
package de.hysky.skyblocker.utils.render.gui;
-import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
+import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.auction.AuctionHouseScreenHandler;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.entity.player.PlayerInventory;
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java
index 80a1ba9d..e2e057b3 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolver.java
@@ -1,11 +1,11 @@
package de.hysky.skyblocker.utils.render.gui;
import de.hysky.skyblocker.SkyblockerMod;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
import net.minecraft.item.ItemStack;
import java.util.List;
-import java.util.Map;
import java.util.regex.Pattern;
/**
@@ -34,9 +34,12 @@ public abstract class ContainerSolver {
SkyblockerMod.getInstance().containerSolverManager.markDirty();
}
- protected abstract List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots);
+ protected void onClickSlot(int slot, ItemStack stack, int screenId, String[] groups) {
+ }
+
+ protected abstract List<ColorHighlight> getColors(String[] groups, Int2ObjectMap<ItemStack> slots);
- protected void trimEdges(Map<Integer, ItemStack> slots, int rows) {
+ protected void trimEdges(Int2ObjectMap<ItemStack> slots, int rows) {
for (int i = 0; i < rows; i++) {
slots.remove(9 * i);
slots.remove(9 * i + 8);
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
index e5b2dddb..b37c57a4 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
@@ -1,7 +1,8 @@
package de.hysky.skyblocker.utils.render.gui;
import com.mojang.blaze3d.systems.RenderSystem;
-import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
+
+import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakeBagHelper;
import de.hysky.skyblocker.skyblock.accessories.newyearcakes.NewYearCakesHelper;
import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper;
@@ -13,6 +14,8 @@ import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver;
import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
import de.hysky.skyblocker.utils.Utils;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
@@ -22,8 +25,6 @@ import net.minecraft.screen.slot.Slot;
import org.jetbrains.annotations.NotNull;
import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -36,6 +37,10 @@ public class ContainerSolverManager {
private ContainerSolver currentSolver = null;
private String[] groups;
private List<ColorHighlight> highlights;
+ /**
+ * Useful for keeping track of a solver's state in a Screen instance, such as if Hypixel closes & reopens a screen after every click (as they do with terminals).
+ */
+ private int screenId = 0;
public ContainerSolverManager() {
solvers = new ContainerSolver[]{
@@ -82,6 +87,7 @@ public class ContainerSolverManager {
matcher.usePattern(solver.getName());
matcher.reset();
if (matcher.matches()) {
+ ++screenId;
currentSolver = solver;
groups = new String[matcher.groupCount()];
for (int i = 0; i < groups.length; i++) {
@@ -89,6 +95,7 @@ public class ContainerSolverManager {
}
currentSolver.start(screen);
markDirty();
+
return;
}
}
@@ -107,6 +114,12 @@ public class ContainerSolverManager {
highlights = null;
}
+ public void onSlotClick(int slot, ItemStack stack) {
+ if (currentSolver != null) {
+ currentSolver.onClickSlot(slot, stack, screenId, groups);
+ }
+ }
+
public void onDraw(DrawContext context, List<Slot> slots) {
if (currentSolver == null)
return;
@@ -122,8 +135,8 @@ public class ContainerSolverManager {
RenderSystem.colorMask(true, true, true, true);
}
- private Map<Integer, ItemStack> slotMap(List<Slot> slots) {
- Map<Integer, ItemStack> slotMap = new TreeMap<>();
+ private Int2ObjectMap<ItemStack> slotMap(List<Slot> slots) {
+ Int2ObjectMap<ItemStack> slotMap = new Int2ObjectRBTreeMap<>();
for (int i = 0; i < slots.size(); i++) {
slotMap.put(i, slots.get(i).getStack());
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java
index 487e3d8b..a115bb2b 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java
@@ -2,10 +2,10 @@ package de.hysky.skyblocker.utils.render.title;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.events.HudRenderEvents;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
-import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
@@ -26,7 +26,7 @@ public class TitleContainer {
private static final Set<Title> titles = new LinkedHashSet<>();
public static void init() {
- HudRenderCallback.EVENT.register(TitleContainer::render);
+ HudRenderEvents.BEFORE_CHAT.register(TitleContainer::render);
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
.then(ClientCommandManager.literal("hud")
.then(ClientCommandManager.literal("titleContainer")
@@ -148,7 +148,7 @@ public class TitleContainer {
//Translate the matrix to the texts position and scale
context.getMatrices().push();
- context.getMatrices().translate(title.x, title.y, 200);
+ context.getMatrices().translate(title.x, title.y, 0);
context.getMatrices().scale(scale, scale, scale);
//Draw text
diff --git a/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java b/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java
new file mode 100644
index 00000000..bd9200ad
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/tictactoe/BoardIndex.java
@@ -0,0 +1,5 @@
+package de.hysky.skyblocker.utils.tictactoe;
+
+public record BoardIndex(int row, int column) {
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java b/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java
index fde18f27..0725b2ce 100644
--- a/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java
@@ -1,105 +1,119 @@
package de.hysky.skyblocker.utils.tictactoe;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
public class TicTacToeUtils {
- public static int getBestMove(char[][] board) {
- HashMap<Integer, Integer> moves = new HashMap<>();
- for (int row = 0; row < board.length; row++) {
- for (int col = 0; col < board[row].length; col++) {
- if (board[row][col] != '\0') continue;
- board[row][col] = 'O';
- int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, false, 0);
- board[row][col] = '\0';
- moves.put(row * 3 + col + 1, score);
- }
- }
- return Collections.max(moves.entrySet(), Map.Entry.comparingByValue()).getKey();
- }
-
- private static boolean hasMovesLeft(char[][] board) {
- for (char[] rows : board) {
- for (char col : rows) {
- if (col == '\0') return true;
- }
- }
- return false;
- }
-
- private static int getBoardScore(char[][] board) {
- for (int row = 0; row < 3; row++) {
- if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) {
- if (board[row][0] == 'X') {
- return -10;
- } else if (board[row][0] == 'O') {
- return 10;
- }
- }
- }
-
- for (int col = 0; col < 3; col++) {
- if (board[0][col] == board[1][col] && board[0][col] == board[2][col]) {
- if (board[0][col] == 'X') {
- return -10;
- } else if (board[0][col] == 'O') {
- return 10;
- }
- }
- }
-
- if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) {
- if (board[0][0] == 'X') {
- return -10;
- } else if (board[0][0] == 'O') {
- return 10;
- }
- } else if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) {
- if (board[0][2] == 'X') {
- return -10;
- } else if (board[0][2] == 'O') {
- return 10;
- }
- }
-
- return 0;
- }
-
- private static int alphabeta(char[][] board, int alpha, int beta, boolean max, int depth) {
- int score = getBoardScore(board);
- if (score == 10 || score == -10) return score;
- if (!hasMovesLeft(board)) return 0;
-
- if (max) {
- int bestScore = Integer.MIN_VALUE;
- for (int row = 0; row < 3; row++) {
- for (int col = 0; col < 3; col++) {
- if (board[row][col] == '\0') {
- board[row][col] = 'O';
- bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, false, depth + 1));
- board[row][col] = '\0';
- alpha = Math.max(alpha, bestScore);
- if (beta <= alpha) break; // Pruning
- }
- }
- }
- return bestScore - depth;
- } else {
- int bestScore = Integer.MAX_VALUE;
- for (int row = 0; row < 3; row++) {
- for (int col = 0; col < 3; col++) {
- if (board[row][col] == '\0') {
- board[row][col] = 'X';
- bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, true, depth + 1));
- board[row][col] = '\0';
- beta = Math.min(beta, bestScore);
- if (beta <= alpha) break; // Pruning
- }
- }
- }
- return bestScore + depth;
- }
- }
+ public static BoardIndex getBestMove(char[][] board) {
+ Object2IntOpenHashMap<BoardIndex> moves = new Object2IntOpenHashMap<>();
+
+ for (int row = 0; row < board.length; row++) {
+ for (int column = 0; column < board[row].length; column++) {
+ // Simulate the move as O if the square is empty to determine a solution
+ if (board[row][column] != '\0') continue;
+ board[row][column] = 'O';
+ int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, false);
+ board[row][column] = '\0';
+
+ moves.put(new BoardIndex(row, column), score);
+ }
+ }
+
+ return Collections.max(moves.object2IntEntrySet(), Comparator.comparingInt(Object2IntMap.Entry::getIntValue)).getKey();
+ }
+
+ private static boolean hasMovesAvailable(char[][] board) {
+ return Arrays.stream(board).flatMap(row -> Stream.of(row[0], row[1], row[2])).anyMatch(c -> c == '\0');
+ }
+
+ private static int getScore(char[][] board) {
+ // Check if X or O has won horizontally
+ for (int row = 0; row < 3; row++) {
+ if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) {
+ switch (board[row][0]) {
+ case 'X': return -10;
+ case 'O': return 10;
+ }
+ }
+ }
+
+ // Check if X or O has won vertically
+ for (int column = 0; column < 3; column++) {
+ if (board[0][column] == board[1][column] && board[0][column] == board[2][column]) {
+ switch (board[0][column]) {
+ case 'X': return -10;
+ case 'O': return 10;
+ }
+ }
+ }
+
+ // Check if X or O has won diagonally
+ // Top left to bottom right
+ if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) {
+ switch (board[0][0]) {
+ case 'X': return -10;
+ case 'O': return 10;
+ }
+ }
+
+ // Top right to bottom left
+ if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) {
+ switch (board[0][2]) {
+ case 'X': return -10;
+ case 'O': return 10;
+ }
+ }
+
+ return 0;
+ }
+
+ private static int alphabeta(char[][] board, int alpha, int beta, int depth, boolean maximizePlayer) {
+ int score = getScore(board);
+
+ if (score == 10 || score == -10) return score;
+ if (!hasMovesAvailable(board)) return 0;
+
+ if (maximizePlayer) {
+ int bestScore = Integer.MIN_VALUE;
+
+ for (int row = 0; row < 3; row++) {
+ for (int column = 0; column < 3; column++) {
+ if (board[row][column] == '\0') {
+ board[row][column] = 'O';
+ bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, depth + 1, false));
+ board[row][column] = '\0';
+ alpha = Math.max(alpha, bestScore);
+
+ //Is this correct? Well the algorithm seems to solve it so I will assume it is
+ if (beta <= alpha) break; // Pruning
+ }
+ }
+ }
+
+ return bestScore - depth;
+ } else {
+ int bestScore = Integer.MAX_VALUE;
+
+ for (int row = 0; row < 3; row++) {
+ for (int column = 0; column < 3; column++) {
+ if (board[row][column] == '\0') {
+ board[row][column] = 'X';
+ bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, depth + 1, true));
+ board[row][column] = '\0';
+ beta = Math.min(beta, bestScore);
+
+ if (beta <= alpha) break; // Pruning
+ }
+ }
+ }
+
+ return bestScore + depth;
+ }
+ }
} \ No newline at end of file