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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package gjum.minecraft.civ.snitchmod.common;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.ClickType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import java.util.HashSet;
import java.util.Set;

public class JalistAutoPaginator {
private static final Minecraft mc = Minecraft.getInstance();
private static JalistAutoPaginator instance;

private boolean isActive = false;
private boolean waitingForNextPage = false;
private int pagesProcessed = 0;
private int totalSnitchesFound = 0;
private int snitchesWillCullSoon = 0; // Count snitches that will cull in next 48h
private int snitchesWillDormantSoon = 0; // Count snitches that will go dormant in next 48h
private Set<String> groupsFound = new HashSet<>();
private long startTime = 0;

// JAList GUI detection
private static final String JALIST_TITLE = "JukeAlert snitches";
private static final int NEXT_PAGE_SLOT = 53; // Bottom right slot in a 54-slot inventory

public static JalistAutoPaginator getInstance() {
if (instance == null) {
instance = new JalistAutoPaginator();
}
return instance;
}

public void startAutoPagination() {
if (isActive) {
logToChat("Auto-pagination already running!");
return;
}

if (!isJalistOpen()) {
logToChat("JAList must be open to start auto-pagination! Press J key while in JAList.");
return;
}

isActive = true;
waitingForNextPage = false;
pagesProcessed = 0;
totalSnitchesFound = 0;
snitchesWillCullSoon = 0;
snitchesWillDormantSoon = 0;
groupsFound.clear();
startTime = System.currentTimeMillis();

logToChat("Starting JAList auto-pagination... This will read all pages automatically.");

// Start the first page click after a short delay
new Thread(() -> {
try {
Thread.sleep(50); // Wait 50ms before starting
if (isActive) {
mc.execute(this::clickNextPage);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}

public void stopAutoPagination() {
if (!isActive) return;

isActive = false;
waitingForNextPage = false;

long duration = (System.currentTimeMillis() - startTime) / 1000;
logToChat(String.format(
"JAList scan complete! Found %d snitches across %d groups in %d pages (%ds)",
totalSnitchesFound, groupsFound.size(), pagesProcessed, duration
));

// Add warnings about snitches that will cull or go dormant soon
if (snitchesWillCullSoon > 0 || snitchesWillDormantSoon > 0) {
StringBuilder warning = new StringBuilder("⚠ Warning: You have ");

if (snitchesWillCullSoon > 0 && snitchesWillDormantSoon > 0) {
warning.append(String.format("%d snitches that will be culled and %d that will go dormant in the next 48h!", snitchesWillCullSoon, snitchesWillDormantSoon));
} else if (snitchesWillCullSoon > 0) {
warning.append(String.format("%d snitches that will be culled in the next 48h!", snitchesWillCullSoon));
} else {
warning.append(String.format("%d snitches that will go dormant in the next 48h!", snitchesWillDormantSoon));
}

warning.append(" Use https://civinfo.net/snitches/map to see where they are!");
logToChat(warning.toString());
}
}

public void onJalistPageLoaded(int snitchCount) {
if (!isActive) return;

pagesProcessed++;
totalSnitchesFound += snitchCount;
waitingForNextPage = false;

// Show progress every 10 pages instead of 5
if (pagesProcessed % 10 == 0) {
logToChat(String.format("Progress: %d pages (%d snitches so far)", pagesProcessed, totalSnitchesFound));
}

// Schedule next page click with very minimal delay for maximum speed
new Thread(() -> {
try {
Thread.sleep(10); // Wait only 10ms between pages for maximum speed
if (isActive) {
mc.execute(this::clickNextPage);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}

public void addSnitchEntry(String group, long dormantTs, long cullTs) {
if (group != null && !group.trim().isEmpty()) {
groupsFound.add(group);
}

long currentTime = System.currentTimeMillis();
long fortyEightHoursFromNow = currentTime + (48L * 60L * 60L * 1000L); // 48 hours in milliseconds

if (cullTs != 0 && cullTs <= fortyEightHoursFromNow) {
snitchesWillCullSoon++;
}

if (dormantTs != 0 && dormantTs <= fortyEightHoursFromNow) {
snitchesWillDormantSoon++;
}
}

private void clickNextPage() {
if (!isActive || waitingForNextPage) {
return;
}

var screen = mc.screen;
if (!(screen instanceof AbstractContainerScreen<?> containerScreen)) {
stopAutoPagination();
return;
}

if (!isJalistOpen()) {
stopAutoPagination();
return;
}

// Get the next page button (arrow in slot 53)
var container = containerScreen.getMenu();

if (container.slots.size() <= NEXT_PAGE_SLOT) {
stopAutoPagination();
return;
}

ItemStack nextPageItem = container.getSlot(NEXT_PAGE_SLOT).getItem();

// Check if there's a next page (should be an arrow item)
if (nextPageItem.isEmpty() || !isArrowItem(nextPageItem)) {
stopAutoPagination();
return;
}

// Click the next page button
waitingForNextPage = true;

// Simulate a left click on the slot
if (mc.gameMode != null) {
mc.gameMode.handleInventoryMouseClick(
container.containerId,
NEXT_PAGE_SLOT,
0,
ClickType.PICKUP,
mc.player
);
}
}

private boolean isJalistOpen() {
var screen = mc.screen;
if (!(screen instanceof AbstractContainerScreen<?> containerScreen)) {
return false;
}

var title = containerScreen.getTitle().getString();
return title.contains("JukeAlert") || title.contains("snitches") || title.toLowerCase().contains("your snitches");
}

private boolean isArrowItem(ItemStack item) {
// Check for common arrow items used for pagination
return item.is(Items.ARROW) ||
item.is(Items.SPECTRAL_ARROW) ||
item.getDisplayName().getString().toLowerCase().contains("next");
}

public boolean isActive() {
return isActive;
}

public boolean isWaitingForNextPage() {
return waitingForNextPage;
}

public static void onTick() {
// No longer needed for this approach
}

public static void onChatMessage(String message) {
// No longer needed for this approach
}

public static boolean isRunning() {
return getInstance().isActive();
}

public static int getCurrentPage() {
return getInstance().pagesProcessed;
}

private void logToChat(String message) {
mc.gui.getChat().addMessage(Component.literal("[JAList Auto] " + message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -546,20 +546,43 @@ private static void renderBoxGuides(AABB box, Color color, float a, float lineWi
* middle center of text is at `pos` before moving it down the screen by `offset`
*/
private static void renderTextFacingCamera(Component text, Vec3 pos, float offset, float scale, int colorAlphaHex) {
// Create a new pose stack for proper 3D positioning
PoseStack poseStack = new PoseStack();

// Translate to the world position
poseStack.translate(pos.x, pos.y, pos.z);

// Make text face the camera
poseStack.mulPose(mc.gameRenderer.getMainCamera().rotation());

// Calculate scale based on distance
scale *= 0.005f * (mc.player.position().distanceTo(pos) / 2.4);
scale = Math.clamp(scale, 0.015f, 0.15f);

// Apply scaling (negative Y to flip text right-side up)
poseStack.scale(scale, -scale, scale);

// Calculate text positioning
float w = mc.font.width(text);
float x = -w / 2f;
float y = -(.5f - offset) * (mc.font.lineHeight + 2); // +2 for background padding, -1 for default line spacing
boolean shadow = false;
scale *= 0.005f * (mc.player.position().distanceTo(pos) / 2.4);
scale = Math.clamp(scale, 0.015f, 0.15f);
Matrix4f matrix = new Matrix4f(eventPoseStack.last().pose());
matrix.scale(scale, -scale, 1); // third component determines background distance

// Get the final transformation matrix
Matrix4f matrix = poseStack.last().pose();

// Background settings - make it more transparent to see text better
float bgOpacity = Minecraft.getInstance().options.getBackgroundOpacity(0.25f);
int bgColor = (int) (bgOpacity * 255.0f) << 24;
int flags = 0;
// XXX somehow, the letters farthest from the crosshair render behind the background
try (RenderBufferGuard guard = RenderBufferGuard.open(false, false, false)) {
mc.font.drawInBatch(text, x, y, colorAlphaHex, shadow, matrix, guard.bufferSource, Font.DisplayMode.SEE_THROUGH, bgColor, flags);

// Ensure text has full alpha if not already set
if ((colorAlphaHex & 0xFF000000) == 0) {
colorAlphaHex |= 0xFF000000; // Add full alpha if missing
}

// Use immediate mode rendering with proper depth handling
try (RenderBufferGuard guard = RenderBufferGuard.open(false, true, false)) {
mc.font.drawInBatch(text, x, y, colorAlphaHex, shadow, matrix, guard.bufferSource, Font.DisplayMode.NORMAL, bgColor, 15728880);
}

/*var poseStack = new PoseStack();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ public abstract class SnitchMod {
"category.snitchmod"
);

protected static final KeyMapping jalistAutoKey = new KeyMapping(
"key.snitchmod.jalistAuto",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_J,
"category.snitchmod"
);

protected static final KeyMapping toggleSnitchGoneStatusKey = new KeyMapping(
"key.snitchmod.toggleSnitchGoneStatusKey",
InputConstants.Type.KEYSYM,
Expand Down Expand Up @@ -145,6 +152,12 @@ public void handleTick() {
logToChat(Component.literal("Reloaded the database"));
}

while (jalistAutoKey.consumeClick()) {
// Start JAList auto-pagination - works even in GUI
System.out.println("[SnitchMod] J key pressed! Current screen: " + (mc.screen != null ? mc.screen.getClass().getSimpleName() : "null"));
JalistAutoPaginator.getInstance().startAutoPagination();
}

while (toggleSnitchGoneStatusKey.consumeClick()) {
if (!getMod().rangeOverlayVisible) {
break;
Expand Down Expand Up @@ -301,6 +314,10 @@ public void handleWindowItems(List<ItemStack> stacks) {
JalistEntry jalistEntry = JalistEntry.fromStack(stack, store.server);
if (jalistEntry != null) {
jalistEntries.add(jalistEntry);
// Notify auto-paginator with actual timestamp data
if (JalistAutoPaginator.getInstance().isActive()) {
JalistAutoPaginator.getInstance().addSnitchEntry(jalistEntry.group, jalistEntry.dormantTs, jalistEntry.cullTs);
}
}
} catch (Throwable e) {
System.err.println("Failed parsing jalist stack " + i + " " + stack);
Expand All @@ -310,7 +327,12 @@ public void handleWindowItems(List<ItemStack> stacks) {
}
store.updateSnitchesFromJalist(jalistEntries);
if (jalistEntries.size() > 0) {
logToChat(Component.literal("Found " + jalistEntries.size() + " snitches on JAList page"));
// Notify auto-paginator that this page was processed
if (JalistAutoPaginator.getInstance().isActive()) {
JalistAutoPaginator.getInstance().onJalistPageLoaded(jalistEntries.size());
} else {
logToChat(Component.literal("Found " + jalistEntries.size() + " snitches on JAList page"));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package gjum.minecraft.civ.snitchmod.fabric;

import gjum.minecraft.civ.snitchmod.common.JalistAutoPaginator;
import gjum.minecraft.civ.snitchmod.common.SnitchMod;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import org.lwjgl.glfw.GLFW;

public class FabricSnitchMod extends SnitchMod implements ClientModInitializer {
@Override
Expand All @@ -14,10 +18,26 @@ public void onInitializeClient() {
KeyBindingHelper.registerKeyBinding(togglePlacementKey);
KeyBindingHelper.registerKeyBinding(previewSnitchFieldKey);
KeyBindingHelper.registerKeyBinding(toggleSnitchGoneStatusKey);
KeyBindingHelper.registerKeyBinding(jalistAutoKey);

ClientTickEvents.START_CLIENT_TICK.register(client -> {
try {
handleTick();

// Check for J key press while in JAList GUI
var mc = Minecraft.getInstance();
if (mc.screen instanceof AbstractContainerScreen<?> containerScreen) {
String title = containerScreen.getTitle().getString();
if ((title.toLowerCase().contains("snitches") || title.contains("JukeAlert"))
&& GLFW.glfwGetKey(mc.getWindow().getWindow(), GLFW.GLFW_KEY_J) == GLFW.GLFW_PRESS) {

// Prevent spam clicking by checking if auto-paginator is not already active
if (!JalistAutoPaginator.getInstance().isActive()) {
System.out.println("[FabricSnitchMod] J key detected in JAList!");
JalistAutoPaginator.getInstance().startAutoPagination();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
Expand Down