Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Garden Tweaks & Features #1118

Merged
merged 21 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,28 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.farming.garden.visitorHelper"))
.binding(defaults.farming.garden.visitorHelper,
.description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelper.@Tooltip")))
.binding(defaults.farming.garden.visitorHelper,
() -> config.farming.garden.visitorHelper,
newValue -> config.farming.garden.visitorHelper = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper.@Tooltip")))
.binding(defaults.farming.garden.showStacksInVisitorHelper,
() -> config.farming.garden.showStacksInVisitorHelper,
newValue -> config.farming.garden.showStacksInVisitorHelper = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly.@Tooltip")))
.binding(defaults.farming.garden.visitorHelperGardenOnly,
() -> config.farming.garden.visitorHelperGardenOnly,
newValue -> config.farming.garden.visitorHelperGardenOnly = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.farming.garden.lockMouseTool"))
.binding(defaults.farming.garden.lockMouseTool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public static class Garden {
@SerialEntry
public boolean visitorHelper = true;

@SerialEntry
public boolean visitorHelperGardenOnly = true;

@SerialEntry
public boolean showStacksInVisitorHelper = false;

@SerialEntry
public boolean lockMouseTool = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ protected HandledScreenMixin(Text title) {

@Inject(at = @At("HEAD"), method = "mouseClicked")
public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) {
if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (!getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) {
VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer);
}
}
Expand Down
258 changes: 143 additions & 115 deletions src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,119 +35,147 @@
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;

public class FarmingHud {
private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class);
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?<xp>\\d+.?\\d*) Farming \\((?<percent>[\\d,]+.?\\d*)%\\)");
private static final MinecraftClient client = MinecraftClient.getInstance();
private static CounterType counterType = CounterType.NONE;
private static final Deque<IntLongPair> counter = new ArrayDeque<>();
private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue();
private static final Queue<FloatLongPair> farmingXp = new ArrayDeque<>();
private static float farmingXpPercentProgress;

@Init
public static void init() {
HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> {
if (shouldRender()) {
if (!counter.isEmpty() && counter.peek().rightLong() + 5000 < System.currentTimeMillis()) {
counter.poll();
}
if (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < System.currentTimeMillis()) {
blockBreaks.dequeueLong();
}
if (!farmingXp.isEmpty() && farmingXp.peek().rightLong() + 1000 < System.currentTimeMillis()) {
farmingXp.poll();
}

ItemStack stack = client.player.getMainHandStack();
if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) {
counterType = CounterType.NONE;
}
}
});
ClientPlayerBlockBreakEvents.AFTER.register((world, player, pos, state) -> {
if (shouldRender()) {
blockBreaks.enqueue(System.currentTimeMillis());
}
});
ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
if (shouldRender() && overlay) {
Matcher matcher = FARMING_XP.matcher(message.getString());
if (matcher.matches()) {
try {
farmingXp.offer(FloatLongPair.of(NUMBER_FORMAT.parse(matcher.group("xp")).floatValue(), System.currentTimeMillis()));
farmingXpPercentProgress = NUMBER_FORMAT.parse(matcher.group("percent")).floatValue();
} catch (ParseException e) {
LOGGER.error("[Skyblocker Farming HUD] Failed to parse farming xp", e);
}
}
}
});
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("hud").then(literal("farming")
.executes(Scheduler.queueOpenScreenCommand(() -> new WidgetsConfigurationScreen(Location.GARDEN, "hud_garden", null)))))));
}

private static boolean tryGetCounter(ItemStack stack, CounterType counterType) {
NbtCompound customData = ItemUtils.getCustomData(stack);
if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false;
int count = customData.getInt(counterType.nbtKey);
if (FarmingHud.counterType != counterType) {
counter.clear();
FarmingHud.counterType = counterType;
}
if (counter.isEmpty() || counter.peekLast().leftInt() != count) {
counter.offer(IntLongPair.of(count, System.currentTimeMillis()));
}
return true;
}

private static boolean shouldRender() {
return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && client.player != null && Utils.getLocation() == Location.GARDEN;
}

public static String counterText() {
return counterType.text;
}

public static int counter() {
return counter.isEmpty() ? 0 : counter.peekLast().leftInt();
}

public static float cropsPerMinute() {
if (counter.isEmpty()) {
return 0;
}
IntLongPair first = counter.peek();
IntLongPair last = counter.peekLast();
return (float) (last.leftInt() - first.leftInt()) / (last.rightLong() - first.rightLong()) * 60_000f;
}

public static int blockBreaks() {
return blockBreaks.size();
}

public static float farmingXpPercentProgress() {
return farmingXpPercentProgress;
}

public static double farmingXpPerHour() {
return farmingXp.stream().mapToDouble(FloatLongPair::leftFloat).sum() * blockBreaks() * 1800; // Hypixel only sends xp updates around every half a second
}

public enum CounterType {
NONE("", "No Counter"),
COUNTER("mined_crops", "Counter: "),
CULTIVATING("farmed_cultivating", "Cultivating Counter: ");

private final String nbtKey;
private final String text;

CounterType(String nbtKey, String text) {
this.nbtKey = nbtKey;
this.text = text;
}
public boolean matchesText(String textToMatch) {
return this.text.equals(textToMatch);
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class);
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?<xp>\\d+(?:\\.\\d+)?) Farming \\((?<percent>[\\d,]+(?:\\.\\d+)?%|[\\d,]+/[\\d,]+)\\)");
private static final MinecraftClient client = MinecraftClient.getInstance();
private static CounterType counterType = CounterType.NONE;
private static final Deque<IntLongPair> counter = new ArrayDeque<>();
private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue();
private static final Queue<FloatLongPair> farmingXp = new ArrayDeque<>();
private static float farmingXpPercentProgress;
private static double smoothedBlocksPerSecond = 0.0;
private static double smoothedFarmingXpPerHour = 0.0;

@Init
public static void init() {
HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> {
if (shouldRender()) {
if (!counter.isEmpty() && counter.peek().rightLong() + 5000 < System.currentTimeMillis()) {
counter.poll();
}
if (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < System.currentTimeMillis()) {
blockBreaks.dequeueLong();
}
if (!farmingXp.isEmpty() && farmingXp.peek().rightLong() + 1000 < System.currentTimeMillis()) {
farmingXp.poll();
}

assert client.player != null;
ItemStack stack = client.player.getMainHandStack();
if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) {
counterType = CounterType.NONE;
}
}
});
ClientPlayerBlockBreakEvents.AFTER.register((world, player, pos, state) -> {
if (shouldRender()) {
blockBreaks.enqueue(System.currentTimeMillis());
}
});
ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
if (shouldRender() && overlay) {
Matcher matcher = FARMING_XP.matcher(message.getString());
if (matcher.matches()) {
try {
farmingXp.offer(FloatLongPair.of(NUMBER_FORMAT.parse(matcher.group("xp")).floatValue(), System.currentTimeMillis()));
farmingXpPercentProgress = NUMBER_FORMAT.parse(matcher.group("percent")).floatValue();
} catch (ParseException e) {
LOGGER.error("[Skyblocker Farming HUD] Failed to parse farming xp", e);
}
}
}
});
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("hud").then(literal("farming")
.executes(Scheduler.queueOpenScreenCommand(() -> new WidgetsConfigurationScreen(Location.GARDEN, "hud_garden", null)))))));
}

private static boolean tryGetCounter(ItemStack stack, CounterType counterType) {
NbtCompound customData = ItemUtils.getCustomData(stack);
if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false;
int count = customData.getInt(counterType.nbtKey);
if (FarmingHud.counterType != counterType) {
counter.clear();
FarmingHud.counterType = counterType;
}
if (counter.isEmpty() || counter.peekLast().leftInt() != count) {
counter.offer(IntLongPair.of(count, System.currentTimeMillis()));
}
return true;
}

private static boolean shouldRender() {
return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && client.player != null && Utils.getLocation() == Location.GARDEN;
}

public static String counterText() {
return counterType.text;
}

public static int counter() {
return counter.isEmpty() ? 0 : counter.peekLast().leftInt();
}

public static float cropsPerMinute() {

if (counter.isEmpty()) {

return 0;

}

IntLongPair first = counter.peek();

IntLongPair last = counter.peekLast();

return (float) (last.leftInt() - first.leftInt()) / (last.rightLong() - first.rightLong()) * 60_000f;

}

public static double blockBreaks() {
long currentTime = System.currentTimeMillis();
while (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < currentTime) {
blockBreaks.dequeueLong();
}

double rawBlocksPerSecond = blockBreaks.size();
double t = 0.01;
smoothedBlocksPerSecond += (rawBlocksPerSecond - smoothedBlocksPerSecond) * t;

return Math.round(smoothedBlocksPerSecond * 10) / 10.0;
}

public static float farmingXpPercentProgress() {
return farmingXpPercentProgress;
}

public static double farmingXpPerHour() {
double xpPerCrop = farmingXp.isEmpty() ? 0 : farmingXp.peek().leftFloat();
double cropsPerSecond = blockBreaks();
double xpPerSecond = xpPerCrop * cropsPerSecond;
double t = 0.1;

smoothedFarmingXpPerHour += (xpPerSecond * 3600 - smoothedFarmingXpPerHour) * t;

return Math.round(smoothedFarmingXpPerHour * 10) / 10.0;
}


public enum CounterType {
NONE("", "No Counter"),
COUNTER("mined_crops", "Counter: "),
CULTIVATING("farmed_cultivating", "Cultivating Counter: ");

private final String nbtKey;
private final String text;

CounterType(String nbtKey, String text) {
this.nbtKey = nbtKey;
this.text = text;
}

public boolean matchesText(String textToMatch) {
return this.text.equals(textToMatch);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ public void updateContent() {
float cropsPerMinute = FarmingHud.cropsPerMinute();
addSimpleIcoText(cropStack, "Crops/min: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) cropsPerMinute / 10 * 10));
addSimpleIcoText(Ico.GOLD, "Coins/h: ", Formatting.GOLD, getPriceText(cropItemId, cropsPerMinute));
addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Integer.toString(FarmingHud.blockBreaks()));
addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Double.toString(FarmingHud.blockBreaks()));
//noinspection DataFlowIssue
addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue()));
addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) FarmingHud.farmingXpPerHour()));
addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format(FarmingHud.farmingXpPerHour()));

Entity cameraEntity = client.getCameraEntity();
String yaw = cameraEntity == null ? "No Camera Entity" : String.format("%.2f", MathHelper.wrapDegrees(cameraEntity.getYaw()));
Expand Down
Loading
Loading