Skip to content

Commit e907dc1

Browse files
committed
Add better user feedback with NHS
1 parent 5d56ece commit e907dc1

10 files changed

Lines changed: 115 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
88
- Semantic Versioning: https://semver.org/spec/v2.0.0.html
99

1010

11+
## [1.3.2] - 2026-02-10
12+
### Added
13+
- Add scan finished message to the player when a network scan is completed, showing the number of issues found in each category.
14+
15+
### Fixed
16+
- Fix localization for ME Network part names in the Network Health Scanner GUI.
17+
18+
1119
## [1.3.1] - 2026-02-07
1220
### Added
1321
- Add multi-session support for Network Health Scanner to allow using multiple scanners on different networks simultaneously.

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ buildscript {
1414

1515
apply plugin: 'net.minecraftforge.gradle.forge'
1616

17-
version = "1.3.0"
17+
version = "1.3.2"
1818
group = "com.ae2powertools"
1919
archivesBaseName = "ae2-powertools"
2020

src/main/java/com/ae2powertools/Tags.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
public class Tags {
77
public static final String MODID = "ae2powertools";
88
public static final String MODNAME = "AE2 PowerTools";
9-
public static final String VERSION = "1.3.0";
9+
public static final String VERSION = "1.3.2";
1010
}

src/main/java/com/ae2powertools/features/scanner/ChannelScanner.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ private void identifyChokepoints() {
380380
World nodeWorld = getNodeWorld(bfsNode.gridNode);
381381
IBlockState blockState = (nodeWorld != null && nodeWorld.isBlockLoaded(pos))
382382
? nodeWorld.getBlockState(pos) : Blocks.AIR.getDefaultState();
383-
String description = getNodeDescription(bfsNode.gridNode.getMachine());
383+
String description = getNodeDescription(bfsNode.gridNode);
384384

385385
ChannelChokepoint chokepoint = new ChannelChokepoint(
386386
pos, dimension, dimName, blockState, description,
@@ -423,7 +423,7 @@ private void identifyMissingChannelDevices() {
423423
// Fall back to empty stack
424424
}
425425

426-
String description = getNodeDescription(node.getMachine());
426+
String description = getNodeDescription(node);
427427
MissingChannelDevice device = new MissingChannelDevice(
428428
pos, dimension, dimName, itemStack, description
429429
);
@@ -439,7 +439,7 @@ private void addConnectionFlows(ChannelChokepoint chokepoint, BfsNode bfsNode) {
439439
if (bfsNode.parent != null) {
440440
BlockPos parentPos = getNodePosition(bfsNode.parent.gridNode);
441441
EnumFacing direction = getConnectionDirection(bfsNode.gridNode, bfsNode.connectionFromParent);
442-
String parentDesc = getNodeDescription(bfsNode.parent.gridNode.getMachine());
442+
String parentDesc = getNodeDescription(bfsNode.parent.gridNode);
443443

444444
// Parent carries all the demand (toward controller)
445445
int parentChannels = bfsNode.connectionFromParent != null
@@ -457,7 +457,7 @@ private void addConnectionFlows(ChannelChokepoint chokepoint, BfsNode bfsNode) {
457457
for (BfsNode child : bfsNode.children) {
458458
BlockPos childPos = getNodePosition(child.gridNode);
459459
EnumFacing direction = getConnectionDirection(bfsNode.gridNode, child.connectionFromParent);
460-
String childDesc = getNodeDescription(child.gridNode.getMachine());
460+
String childDesc = getNodeDescription(child.gridNode);
461461

462462
int childChannels = child.connectionFromParent != null
463463
? child.connectionFromParent.getUsedChannels() : 0;
@@ -513,8 +513,22 @@ private BlockPos getNodePosition(IGridNode node) {
513513

514514
/**
515515
* Get a human-readable description of a grid node.
516+
* Uses the machine representation's display name for localized output.
516517
*/
517-
private String getNodeDescription(IGridHost host) {
518+
private String getNodeDescription(IGridNode node) {
519+
if (node == null) return I18n.translateToLocal("ae2powertools.common.unknown");
520+
521+
// Try to get the localized name from the machine representation
522+
try {
523+
ItemStack representation = node.getGridBlock().getMachineRepresentation();
524+
525+
if (!representation.isEmpty()) return representation.getDisplayName();
526+
} catch (Exception e) {
527+
// Fall through to class name fallback
528+
}
529+
530+
// Fallback: clean up class name
531+
IGridHost host = node.getMachine();
518532
if (host == null) return I18n.translateToLocal("ae2powertools.common.unknown");
519533

520534
String className = host.getClass().getSimpleName();

src/main/java/com/ae2powertools/features/scanner/GuiNetworkHealthScanner.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public class GuiNetworkHealthScanner extends GuiScreen {
8888
private GuiButton deselectAllButton;
8989
private GuiButton cancelButton;
9090

91+
// TODO: add sort button (distance/A-Z/Z-A)
92+
9193
/**
9294
* Represents a row in the display list.
9395
*/
@@ -309,6 +311,8 @@ private void rebuildDisplayRows() {
309311
}
310312

311313
private void rebuildLoopRows() {
314+
// TODO: Add a message telling loops are not a "hard" issue and can be ignored as long as nothing looks wrong in the network.
315+
312316
List<LoopLocationClient> sorted = ScannerClientState.getSortedLoopLocations();
313317
List<LoopLocationClient> original = ScannerClientState.getLoopLocations();
314318
if (sorted.isEmpty()) return;

src/main/java/com/ae2powertools/features/scanner/NetworkScanner.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import net.minecraft.block.state.IBlockState;
1111
import net.minecraft.init.Blocks;
12+
import net.minecraft.item.ItemStack;
1213
import net.minecraft.tileentity.TileEntity;
1314
import net.minecraft.util.math.BlockPos;
1415
import net.minecraft.util.math.ChunkPos;
@@ -319,7 +320,7 @@ private void addLoopLocation(IGridConnection connection, PathNode current, PathN
319320
boolean isLoaded = nodeWorld != null && nodeWorld.isBlockLoaded(pos);
320321
IBlockState blockState = isLoaded ? nodeWorld.getBlockState(pos) : Blocks.AIR.getDefaultState();
321322

322-
String description = getNodeDescription(host);
323+
String description = getNodeDescription(node);
323324
IssueLocation loopLoc = new IssueLocation(pos, dimension, dimName, blockState, isLoaded, description);
324325
detectedLoops.add(loopLoc);
325326
}
@@ -340,7 +341,7 @@ private void addClusterLoopLocation(BlockPos pos, IGridHost host, IGridNode node
340341

341342
if (isLoaded) blockState = nodeWorld.getBlockState(pos);
342343

343-
String description = getNodeDescription(host);
344+
String description = getNodeDescription(node);
344345
IssueLocation loopLoc = new IssueLocation(pos, dimension, dimName, blockState, isLoaded, description);
345346
detectedLoops.add(loopLoc);
346347
}
@@ -405,15 +406,26 @@ private BlockPos getNodePosition(IGridNode node) {
405406

406407
/**
407408
* Get a human-readable description of a grid node.
409+
* Uses the machine representation's display name for localized output.
408410
*/
409-
private String getNodeDescription(IGridHost host) {
411+
private String getNodeDescription(IGridNode node) {
412+
if (node == null) return I18n.translateToLocal("ae2powertools.common.unknown");
413+
414+
// Try to get the localized name from the machine representation
415+
try {
416+
ItemStack representation = node.getGridBlock().getMachineRepresentation();
417+
418+
if (!representation.isEmpty()) return representation.getDisplayName();
419+
} catch (Exception e) {
420+
// Fall through to class name fallback
421+
}
422+
423+
// Fallback: clean up class name
424+
IGridHost host = node.getMachine();
410425
if (host == null) return I18n.translateToLocal("ae2powertools.common.unknown");
411426

412427
String className = host.getClass().getSimpleName();
413428

414-
// TODO: should we try to localize some common AE2 part names here?
415-
// Or maybe we can get the part name from the host if it's a part?
416-
// Clean up common names
417429
if (className.startsWith("Tile")) {
418430
className = className.substring(4);
419431
} else if (className.startsWith("Part")) {

src/main/java/com/ae2powertools/features/scanner/ScanSessionManager.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import net.minecraft.entity.player.EntityPlayer;
1111
import net.minecraft.util.math.BlockPos;
12+
import net.minecraft.util.text.ITextComponent;
13+
import net.minecraft.util.text.TextComponentTranslation;
1214
import net.minecraft.util.text.translation.I18n;
1315

1416
import appeng.api.networking.IGrid;
@@ -57,6 +59,7 @@ public int hashCode() {
5759
}
5860

5961
private static final Map<SessionKey, ScanSession> sessions = new HashMap<>();
62+
private static final List<SessionKey> completedThisTick = new ArrayList<>();
6063

6164
/**
6265
* Represents an active scan session for a player.
@@ -248,8 +251,47 @@ public static Iterable<Map.Entry<SessionKey, ScanSession>> getSessionEntries() {
248251
* Call this from server tick handler.
249252
*/
250253
public static void tickSessions() {
251-
for (ScanSession session : sessions.values()) {
252-
if (!session.getScanner().isComplete()) session.getScanner().processBatch();
254+
completedThisTick.clear();
255+
256+
for (Map.Entry<SessionKey, ScanSession> entry : sessions.entrySet()) {
257+
ScanSession session = entry.getValue();
258+
if (session.getScanner().isComplete()) continue;
259+
260+
session.getScanner().processBatch();
261+
262+
// Track if this session just completed
263+
if (session.getScanner().isComplete()) {
264+
completedThisTick.add(entry.getKey());
265+
}
253266
}
254267
}
268+
269+
/**
270+
* Get the list of session keys that completed during the last tick.
271+
*/
272+
public static List<SessionKey> getCompletedThisTick() {
273+
return completedThisTick;
274+
}
275+
276+
/**
277+
* Build the completion message for a scan session.
278+
*/
279+
public static ITextComponent buildCompletionMessage(ScanSession session) {
280+
NetworkScanner scanner = session.getScanner();
281+
int nodes = scanner.getNodesProcessed();
282+
int loops = scanner.getDetectedLoops().size();
283+
int chunks = scanner.getUnloadedChunks().size();
284+
int chokepoints = scanner.getChokepoints().size();
285+
int missing = scanner.getMissingDevices().size();
286+
287+
// Build the message as a single component with newlines
288+
StringBuilder messageBuilder = new StringBuilder();
289+
messageBuilder.append(I18n.translateToLocalFormatted("ae2powertools.scanner.complete.summary.top", nodes)).append("\n");
290+
messageBuilder.append(I18n.translateToLocalFormatted("ae2powertools.scanner.complete.summary.line1", loops)).append("\n");
291+
messageBuilder.append(I18n.translateToLocalFormatted("ae2powertools.scanner.complete.summary.line2", chunks)).append("\n");
292+
messageBuilder.append(I18n.translateToLocalFormatted("ae2powertools.scanner.complete.summary.line3", chokepoints)).append("\n");
293+
messageBuilder.append(I18n.translateToLocalFormatted("ae2powertools.scanner.complete.summary.line4", missing));
294+
295+
return new TextComponentTranslation(messageBuilder.toString());
296+
}
255297
}

src/main/java/com/ae2powertools/features/scanner/ScannerRenderer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class ScannerRenderer {
4949
private static final int LINE_SPACING = 2;
5050

5151
// ========== Arrow Rendering Constants ==========
52+
// TODO: Adapt these values to the screen resolution for better visibility
5253
private static final float ARROW_BASE_DISTANCE = 0.6f;
5354
private static final float ARROW_SPREAD_RADIUS = 0.12f;
5455
private static final float ARROW_LENGTH = 0.05f;

src/main/java/com/ae2powertools/features/scanner/ScannerTickHandler.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ public void onServerTick(TickEvent.ServerTickEvent event) {
3434
// Process all active scan sessions
3535
ScanSessionManager.tickSessions();
3636

37+
// Send completion messages for sessions that just finished
38+
for (ScanSessionManager.SessionKey key : ScanSessionManager.getCompletedThisTick()) {
39+
EntityPlayerMP player = findPlayer(server, key.getPlayerId());
40+
41+
if (player != null) {
42+
ScanSessionManager.ScanSession session = ScanSessionManager.getSession(key.getPlayerId(), key.getDeviceId());
43+
if (session != null) {
44+
player.sendMessage(ScanSessionManager.buildCompletionMessage(session));
45+
}
46+
}
47+
}
48+
3749
// Sync to clients periodically
3850
Set<ScanSessionManager.SessionKey> toRemove = new HashSet<>();
3951

src/main/resources/assets/ae2powertools/lang/en_US.lang

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,12 @@ ae2powertools.scanner.channel.found=Found %d chokepoints, %d missing devices.
7474
ae2powertools.scanner.channel.calculating_progress=Analyzing channels... %d/%d nodes.
7575
ae2powertools.scanner.channel.to_controller=(→Controller)
7676

77+
# Scan Completion Messages
78+
ae2powertools.scanner.complete.summary.top=Scanned %d nodes:
79+
ae2powertools.scanner.complete.summary.line1=- Found %d loop(s)
80+
ae2powertools.scanner.complete.summary.line2=- Found %d non-chunkloaded chunk(s)
81+
ae2powertools.scanner.complete.summary.line3=- Found %d chokepoint(s)
82+
ae2powertools.scanner.complete.summary.line4=- Found %d device(s) without channels
83+
7784
# Common strings
7885
ae2powertools.common.unknown=Unknown

0 commit comments

Comments
 (0)