Skip to content

Commit b1702a5

Browse files
committed
Add Subnet Proxy diagnostic gated by config
1 parent bcd16df commit b1702a5

7 files changed

Lines changed: 661 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
1717
### Added
1818
- Add textures for IO Interfaces, Insertion Card, and EMC Cell.
1919
- Add an EMC Cell reported-amount config in CELLS so the visible stack size no longer depends on ProjectEX's external cap and can be set up to Long.MAX_VALUE.
20+
- Add targeted Subnet Proxy diagnostics: a rate-limited warning when the proxy detects a failed extract while the item is still listed, plus `/inspectSubnetProxy` to inspect the looked-at proxy's live state and last detected mismatch. Warnings and fault-recording are gated behind an opt-in config, to allow toggling it on only when needed. Faults originate from ghost items or unextractable items, but the exact cause may not come from the proxy itself. Some things may misreport content (e.g., Essentia Storage Bus) or have items that are visible but not extractable (e.g., EMC Link without enough EMC).
2021

2122
### Fixed
2223
- Fix Subnet Proxy occasionally keeping stale availability after back-grid topology changes, by rebuilding from AE2-active providers and forcing a front-grid refresh when the proxy's published source/election surface actually changes.

src/main/java/com/cells/Cells.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.cells.cells.configurable.ComponentHelper;
2323
import com.cells.commands.FillCellCommand;
24+
import com.cells.commands.InspectSubnetProxyCommand;
2425
import com.cells.commands.InspectSlotCommand;
2526
import com.cells.config.CellsConfig;
2627
import com.cells.gui.CellsGuiHandler;
@@ -101,5 +102,6 @@ public void postInit(FMLPostInitializationEvent event) {
101102
public void serverStarting(FMLServerStartingEvent event) {
102103
event.registerServerCommand(new FillCellCommand());
103104
event.registerServerCommand(new InspectSlotCommand());
105+
event.registerServerCommand(new InspectSubnetProxyCommand());
104106
}
105107
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package com.cells.commands;
2+
3+
import java.util.EnumSet;
4+
5+
import javax.annotation.Nonnull;
6+
7+
import net.minecraft.command.CommandBase;
8+
import net.minecraft.command.ICommandSender;
9+
import net.minecraft.entity.player.EntityPlayerMP;
10+
import net.minecraft.server.MinecraftServer;
11+
import net.minecraft.tileentity.TileEntity;
12+
import net.minecraft.util.EnumFacing;
13+
import net.minecraft.util.math.BlockPos;
14+
import net.minecraft.util.math.RayTraceResult;
15+
import net.minecraft.util.math.Vec3d;
16+
import net.minecraft.util.text.TextComponentTranslation;
17+
18+
import appeng.api.parts.IPart;
19+
import appeng.api.parts.IPartHost;
20+
import appeng.api.parts.SelectedPart;
21+
import appeng.api.util.AEPartLocation;
22+
23+
import com.cells.network.sync.ResourceType;
24+
import com.cells.parts.subnetproxy.PartSubnetProxyBack;
25+
import com.cells.parts.subnetproxy.PartSubnetProxyFront;
26+
27+
28+
public class InspectSubnetProxyCommand extends CommandBase {
29+
30+
private static final double REACH_DISTANCE = 5.0D;
31+
32+
@Override
33+
@Nonnull
34+
public String getName() {
35+
return "inspectSubnetProxy";
36+
}
37+
38+
@Override
39+
@Nonnull
40+
public String getUsage(@Nonnull ICommandSender sender) {
41+
return "commands.cells.inspect_subnet_proxy.usage";
42+
}
43+
44+
@Override
45+
public int getRequiredPermissionLevel() {
46+
return 0;
47+
}
48+
49+
@Override
50+
public void execute(@Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args) {
51+
if (!(sender.getCommandSenderEntity() instanceof EntityPlayerMP)) {
52+
sender.sendMessage(new TextComponentTranslation("commands.cells.inspect_slots.not_player"));
53+
return;
54+
}
55+
56+
EntityPlayerMP player = (EntityPlayerMP) sender.getCommandSenderEntity();
57+
RayTraceResult hit = rayTrace(player);
58+
if (hit == null || hit.typeOfHit != RayTraceResult.Type.BLOCK) {
59+
sender.sendMessage(new TextComponentTranslation("commands.cells.inspect_slots.no_block"));
60+
return;
61+
}
62+
63+
BlockPos hitPos = hit.getBlockPos();
64+
TileEntity te = player.world.getTileEntity(hitPos);
65+
if (te == null) {
66+
sender.sendMessage(new TextComponentTranslation(
67+
"commands.cells.inspect_slots.no_tile", hitPos.getX(), hitPos.getY(), hitPos.getZ()));
68+
return;
69+
}
70+
71+
TargetedProxy target = resolveTarget(te, hit);
72+
if (target == null) {
73+
sender.sendMessage(new TextComponentTranslation("commands.cells.inspect_subnet_proxy.no_proxy"));
74+
return;
75+
}
76+
77+
if (target.targetedBack) {
78+
sender.sendMessage(new TextComponentTranslation(
79+
"commands.cells.inspect_subnet_proxy.back_header",
80+
hitPos.getX(),
81+
hitPos.getY(),
82+
hitPos.getZ(),
83+
player.world.provider.getDimension(),
84+
player.world.provider.getDimensionType().getName(),
85+
target.back.getSide().getFacing()));
86+
87+
if (target.front == null) {
88+
sender.sendMessage(new TextComponentTranslation("commands.cells.inspect_subnet_proxy.no_front"));
89+
return;
90+
}
91+
}
92+
93+
PartSubnetProxyFront.ProxyDiagnosticSnapshot snapshot = target.front.getDiagnosticSnapshot();
94+
BlockPos frontPos = snapshot.pos != null ? snapshot.pos : hitPos;
95+
EnumFacing frontSide = snapshot.side != null ? snapshot.side : hit.sideHit;
96+
97+
if (!target.targetedBack) {
98+
sender.sendMessage(new TextComponentTranslation(
99+
"commands.cells.inspect_subnet_proxy.front_header",
100+
frontPos.getX(),
101+
frontPos.getY(),
102+
frontPos.getZ(),
103+
snapshot.dimensionId,
104+
snapshot.dimensionName,
105+
frontSide));
106+
} else {
107+
sender.sendMessage(new TextComponentTranslation(
108+
"commands.cells.inspect_subnet_proxy.linked_front",
109+
frontPos.getX(),
110+
frontPos.getY(),
111+
frontPos.getZ(),
112+
frontSide));
113+
}
114+
115+
sender.sendMessage(new TextComponentTranslation(
116+
"commands.cells.inspect_subnet_proxy.state",
117+
boolText(snapshot.powered),
118+
boolText(snapshot.active),
119+
boolText(snapshot.paired),
120+
boolText(snapshot.ownOriginVisible),
121+
formatHash(snapshot.structureHash)));
122+
123+
sender.sendMessage(new TextComponentTranslation(
124+
"commands.cells.inspect_subnet_proxy.channels",
125+
describeEnabledChannels(snapshot.enabledChannels),
126+
snapshot.priority,
127+
boolText(snapshot.hasInsertionCard),
128+
boolText(snapshot.insertionActive),
129+
snapshot.filterMode.name(),
130+
boolText(snapshot.fuzzyEnabled),
131+
boolText(snapshot.inverterEnabled)));
132+
133+
sender.sendMessage(new TextComponentTranslation(
134+
"commands.cells.inspect_subnet_proxy.grids",
135+
formatHash(snapshot.frontGridHash),
136+
formatHash(snapshot.backGridHash),
137+
snapshot.exposedOriginCount,
138+
snapshot.visiblePeerCount,
139+
snapshot.totalPeerCount,
140+
snapshot.itemLocalCells,
141+
snapshot.fluidLocalCells,
142+
snapshot.gasLocalCells,
143+
snapshot.essentiaLocalCells));
144+
145+
if (snapshot.lastFault == null) {
146+
sender.sendMessage(new TextComponentTranslation("commands.cells.inspect_subnet_proxy.last_fault.none"));
147+
return;
148+
}
149+
150+
sender.sendMessage(new TextComponentTranslation(
151+
"commands.cells.inspect_subnet_proxy.last_fault",
152+
snapshot.lastFault.lastObservedTick,
153+
snapshot.lastFault.occurrenceCount,
154+
snapshot.lastFault.channelName,
155+
snapshot.lastFault.requestDescription,
156+
snapshot.lastFault.requestedAmount,
157+
snapshot.lastFault.extractedAmount,
158+
snapshot.lastFault.visibleAmount,
159+
snapshot.lastFault.actionName,
160+
formatHash(snapshot.lastFault.structureHash)));
161+
}
162+
163+
private static TargetedProxy resolveTarget(TileEntity te, RayTraceResult hit) {
164+
if (!(te instanceof IPartHost)) return null;
165+
166+
IPartHost host = (IPartHost) te;
167+
SelectedPart selected = host.selectPartGlobal(hit.hitVec);
168+
IPart part = selected.part;
169+
170+
if (!(part instanceof PartSubnetProxyFront) && !(part instanceof PartSubnetProxyBack) && hit.sideHit != null) {
171+
part = host.getPart(AEPartLocation.fromFacing(hit.sideHit));
172+
}
173+
174+
if (part instanceof PartSubnetProxyFront) {
175+
PartSubnetProxyFront front = (PartSubnetProxyFront) part;
176+
return new TargetedProxy(front, front.findBackPart(), false);
177+
}
178+
179+
if (part instanceof PartSubnetProxyBack) {
180+
PartSubnetProxyBack back = (PartSubnetProxyBack) part;
181+
return new TargetedProxy(back.findFrontPart(), back, true);
182+
}
183+
184+
return null;
185+
}
186+
187+
private static RayTraceResult rayTrace(EntityPlayerMP player) {
188+
Vec3d start = player.getPositionEyes(1.0F);
189+
Vec3d lookVec = player.getLookVec();
190+
Vec3d end = start.add(lookVec.scale(REACH_DISTANCE));
191+
192+
return player.world.rayTraceBlocks(start, end, false, false, true);
193+
}
194+
195+
private static String boolText(boolean value) {
196+
return value ? "yes" : "no";
197+
}
198+
199+
private static String formatHash(int hash) {
200+
return String.format("%08X", hash);
201+
}
202+
203+
private static String describeEnabledChannels(EnumSet<ResourceType> channels) {
204+
if (channels.isEmpty()) return "none";
205+
206+
StringBuilder description = new StringBuilder();
207+
for (ResourceType channel : channels) {
208+
if (description.length() > 0) description.append(", ");
209+
210+
description.append(channel.name());
211+
}
212+
213+
return description.toString();
214+
}
215+
216+
private static class TargetedProxy {
217+
218+
private final PartSubnetProxyFront front;
219+
private final PartSubnetProxyBack back;
220+
private final boolean targetedBack;
221+
222+
private TargetedProxy(PartSubnetProxyFront front, PartSubnetProxyBack back, boolean targetedBack) {
223+
this.front = front;
224+
this.back = back;
225+
this.targetedBack = targetedBack;
226+
}
227+
}
228+
}

src/main/java/com/cells/config/CellsConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ public class CellsConfig {
159159
/** Maximum tick rate for the Subnet Proxy (ticks between updates when idle) */
160160
public static int subnetProxyMaxTickRate = 60;
161161

162+
/** Enable Subnet Proxy extraction-fault reporting and warning logs */
163+
public static boolean subnetProxyReportExtractionFaults = false;
164+
162165
/** Essentia Creative Cell fix */
163166
public static boolean enableEssentiaCreativeCellFix = true;
164167

@@ -504,6 +507,14 @@ public static void loadConfig() {
504507
p.setLanguageKey(Tags.MODID + ".config.subnetProxyMaxTickRate");
505508
subnetProxyMaxTickRate = p.getInt();
506509

510+
p = config.get(CATEGORY_GENERAL,
511+
"subnetProxyReportExtractionFaults", false,
512+
"Enable Subnet Proxy extraction-fault reporting and warning logs. " +
513+
"A reported fault can originate from the proxy, a connected inventory, or the network itself."
514+
);
515+
p.setLanguageKey(Tags.MODID + ".config.subnetProxyReportExtractionFaults");
516+
subnetProxyReportExtractionFaults = p.getBoolean();
517+
507518
// Hidden category: GUI preferences (not shown in config GUI)
508519
p = config.get(CATEGORY_HIDDEN, "showControlsHelp", false,
509520
"Whether the controls help panel is visible in Interface GUIs.");

0 commit comments

Comments
 (0)