33import com .researchcube .ResearchCubeMod ;
44import com .researchcube .block .ResearchTableBlockEntity ;
55import com .researchcube .menu .ResearchTableMenu ;
6+ import com .researchcube .network .CancelResearchPacket ;
67import com .researchcube .network .StartResearchPacket ;
8+ import com .researchcube .network .WipeTankPacket ;
79import com .researchcube .registry .ModFluids ;
810import com .researchcube .research .FluidCost ;
911import com .researchcube .research .ItemCost ;
1416import com .researchcube .research .prerequisite .OrPrerequisite ;
1517import com .researchcube .research .prerequisite .Prerequisite ;
1618import com .researchcube .research .prerequisite .SinglePrerequisite ;
19+ import com .researchcube .util .IdeaChipMatcher ;
1720import net .minecraft .client .Minecraft ;
1821import net .minecraft .client .gui .GuiGraphics ;
1922import net .minecraft .client .gui .components .Button ;
2023import net .minecraft .client .gui .screens .inventory .AbstractContainerScreen ;
2124import net .minecraft .network .chat .Component ;
2225import net .minecraft .resources .ResourceLocation ;
2326import net .minecraft .world .entity .player .Inventory ;
27+ import net .minecraft .world .item .Item ;
2428import net .minecraft .world .item .ItemStack ;
2529import net .neoforged .neoforge .network .PacketDistributor ;
2630
@@ -103,10 +107,9 @@ private record Dependency(ResourceLocation sourceId, EdgeStyle style) {}
103107 private double dragLastY ;
104108
105109 private Button startButton ;
110+ private Button cancelButton ;
111+ private Button wipeButton ;
106112 private Button listButton ;
107- private Button fitButton ;
108- private Button zoomInButton ;
109- private Button zoomOutButton ;
110113
111114 private int graphMinX ;
112115 private int graphMinY ;
@@ -128,29 +131,28 @@ public ResearchTreeScreen(ResearchTableMenu menu, Inventory playerInv, Component
128131 protected void init () {
129132 super .init ();
130133
131- // Buttons positioned in upper panel controls area
132- int btnY = topPos + ResearchTableMenu .TREE_BTN_Y ;
133-
134+ // Start button
134135 this .startButton = addRenderableWidget (Button .builder (Component .literal ("Start" ), b -> onStartResearch ())
135136 .bounds (leftPos + ResearchTableMenu .START_BTN_X , topPos + ResearchTableMenu .BUTTON_Y ,
136137 ResearchTableMenu .BUTTON_W , ResearchTableMenu .BUTTON_H )
137138 .build ());
138139
139- this .listButton = addRenderableWidget (Button .builder (Component .literal ("List" ), b -> openListView ())
140- .bounds (leftPos + ResearchTableMenu .LIST_BTN_X , btnY ,
141- ResearchTableMenu .LIST_BTN_W , ResearchTableMenu .LIST_BTN_H )
142- .build ());
143-
144- this .fitButton = addRenderableWidget (Button .builder (Component .literal ("Fit" ), b -> fitGraphToViewport ())
145- .bounds (leftPos + ResearchTableMenu .SEARCH_X , btnY , 30 , 16 )
140+ // Cancel/Stop button
141+ this .cancelButton = addRenderableWidget (Button .builder (Component .literal ("Stop" ), b -> onCancelResearch ())
142+ .bounds (leftPos + ResearchTableMenu .STOP_BTN_X , topPos + ResearchTableMenu .BUTTON_Y ,
143+ ResearchTableMenu .BUTTON_W , ResearchTableMenu .BUTTON_H )
146144 .build ());
147145
148- this .zoomOutButton = addRenderableWidget (Button .builder (Component .literal ("-" ), b -> adjustZoom (-0.12f ))
149- .bounds (leftPos + ResearchTableMenu .SEARCH_X + 34 , btnY , 20 , 16 )
146+ // Wipe tank button
147+ this .wipeButton = addRenderableWidget (Button .builder (Component .literal ("Wipe" ), b -> onWipeTank ())
148+ .bounds (leftPos + ResearchTableMenu .WIPE_BTN_X , topPos + ResearchTableMenu .BUTTON_Y ,
149+ ResearchTableMenu .BUTTON_W , ResearchTableMenu .BUTTON_H )
150150 .build ());
151151
152- this .zoomInButton = addRenderableWidget (Button .builder (Component .literal ("+" ), b -> adjustZoom (0.12f ))
153- .bounds (leftPos + ResearchTableMenu .SEARCH_X + 58 , btnY , 20 , 16 )
152+ // List view button (switches back to list view)
153+ this .listButton = addRenderableWidget (Button .builder (Component .literal ("List" ), b -> openListView ())
154+ .bounds (leftPos + ResearchTableMenu .LIST_BTN_X , topPos + ResearchTableMenu .TREE_BTN_Y ,
155+ ResearchTableMenu .LIST_BTN_W , ResearchTableMenu .LIST_BTN_H )
154156 .build ());
155157
156158 buildGraph ();
@@ -174,12 +176,12 @@ public void containerTick() {
174176 clampPan ();
175177 }
176178
177- boolean canStart = !menu .isResearching () && selectedId != null ;
178- if (canStart ) {
179- ResearchDefinition selected = ResearchRegistry .get (selectedId );
180- canStart = selected != null && isPrerequisiteMet (selected );
181- }
179+ // Update button states (same logic as ResearchTableScreen)
180+ ResearchDefinition selectedDef = getSelectedDefinition ();
181+ boolean canStart = !menu .isResearching () && selectedDef != null && canStartResearch (selectedDef );
182182 startButton .active = canStart ;
183+ cancelButton .active = menu .isResearching ();
184+ wipeButton .active = menu .getFluidAmount () > 0 ;
183185 }
184186
185187 private void openListView () {
@@ -191,12 +193,70 @@ private void openListView() {
191193 private void onStartResearch () {
192194 if (selectedId == null || menu .isResearching ()) return ;
193195 ResearchDefinition def = ResearchRegistry .get (selectedId );
194- if (def == null || !isPrerequisiteMet (def )) return ;
196+ if (def == null || !canStartResearch (def )) return ;
195197
196198 ResearchTableBlockEntity be = menu .getBlockEntity ();
197199 PacketDistributor .sendToServer (new StartResearchPacket (be .getBlockPos (), selectedId .toString ()));
198200 }
199201
202+ private void onCancelResearch () {
203+ if (!menu .isResearching ()) return ;
204+ ResearchTableBlockEntity be = menu .getBlockEntity ();
205+ PacketDistributor .sendToServer (new CancelResearchPacket (be .getBlockPos ()));
206+ }
207+
208+ private void onWipeTank () {
209+ ResearchTableBlockEntity be = menu .getBlockEntity ();
210+ PacketDistributor .sendToServer (new WipeTankPacket (be .getBlockPos ()));
211+ }
212+
213+ private boolean canStartResearch (ResearchDefinition def ) {
214+ if (!isPrerequisiteMet (def )) return false ;
215+ if (!isIdeaChipSatisfied (def )) return false ;
216+ if (!hasRequiredItems (def )) return false ;
217+ if (!hasRequiredFluid (def )) return false ;
218+ return true ;
219+ }
220+
221+ private boolean hasRequiredItems (ResearchDefinition def ) {
222+ if (def .getItemCosts ().isEmpty ()) return true ;
223+ Map <Item , Integer > available = new HashMap <>();
224+ for (int i = 0 ; i < 6 ; i ++) {
225+ int slotIndex = ResearchTableBlockEntity .COST_SLOT_START + i ;
226+ ItemStack stack = menu .getSlot (slotIndex ).getItem ();
227+ if (!stack .isEmpty ()) {
228+ available .merge (stack .getItem (), stack .getCount (), Integer ::sum );
229+ }
230+ }
231+ for (ItemCost cost : def .getItemCosts ()) {
232+ int availableCount = available .getOrDefault (cost .getItem (), 0 );
233+ if (availableCount < cost .count ()) return false ;
234+ }
235+ return true ;
236+ }
237+
238+ private boolean hasRequiredFluid (ResearchDefinition def ) {
239+ FluidCost fluidCost = def .getFluidCost ();
240+ if (fluidCost == null ) return true ;
241+ int tankFluidType = menu .getFluidType ();
242+ int tankAmount = menu .getFluidAmount ();
243+ int requiredType = ModFluids .getFluidIndex (fluidCost .getFluid ());
244+ if (tankFluidType != requiredType ) return false ;
245+ return tankAmount >= fluidCost .amount ();
246+ }
247+
248+ private boolean isIdeaChipSatisfied (ResearchDefinition def ) {
249+ if (def .getIdeaChip ().isEmpty ()) return true ;
250+ ItemStack required = def .getIdeaChip ().get ();
251+ ItemStack candidate = menu .getSlot (ResearchTableBlockEntity .SLOT_IDEA_CHIP ).getItem ();
252+ return IdeaChipMatcher .matches (required , candidate );
253+ }
254+
255+ private ResearchDefinition getSelectedDefinition () {
256+ if (selectedId == null ) return null ;
257+ return ResearchRegistry .get (selectedId );
258+ }
259+
200260 private boolean isPrerequisiteMet (ResearchDefinition def ) {
201261 return def .getPrerequisites ().isSatisfied (menu .getCompletedResearch ());
202262 }
@@ -418,6 +478,18 @@ protected void renderBg(GuiGraphics g, float partialTick, int mouseX, int mouseY
418478 // ── Static background from texture (same as ResearchTableScreen) ──
419479 g .blit (TEXTURE , x , y , 0 , 0 , imageWidth , imageHeight , TEX_W , TEX_H );
420480
481+ // ── Slot labels (above slot row) ──
482+ int labelY = y + ResearchTableMenu .LABEL_Y ;
483+ g .drawString (font , "Dr" , x + ResearchTableMenu .DRIVE_X + 2 , labelY , 0xFFD3D7E5 , false );
484+ g .drawString (font , "Cb" , x + ResearchTableMenu .CUBE_X + 2 , labelY , 0xFFD3D7E5 , false );
485+ g .drawString (font , "Id" , x + ResearchTableMenu .IDEA_CHIP_X + 2 , labelY , 0xFFD3D7E5 , false );
486+ g .drawString (font , "Costs" , x + ResearchTableMenu .COST_X , labelY , 0xFFD3D7E5 , false );
487+ g .drawString (font , "Fl" , x + ResearchTableMenu .FLUID_GAUGE_X + 3 , labelY , 0xFFD3D7E5 , false );
488+ g .drawString (font , "I/O" , x + ResearchTableMenu .BUCKET_IN_X , labelY , 0xFFD3D7E5 , false );
489+
490+ // ── Idea chip dynamic overlay ──
491+ renderIdeaChipOverlay (g , x + ResearchTableMenu .IDEA_CHIP_X , y + ResearchTableMenu .IDEA_CHIP_Y );
492+
421493 // ── Fluid gauge (dynamic) ──
422494 drawFluidGauge (g , x + ResearchTableMenu .FLUID_GAUGE_X , y + ResearchTableMenu .FLUID_GAUGE_Y ,
423495 ResearchTableMenu .FLUID_GAUGE_W , ResearchTableMenu .FLUID_GAUGE_H );
@@ -435,6 +507,28 @@ protected void renderBg(GuiGraphics g, float partialTick, int mouseX, int mouseY
435507 drawEdges (g , gx , gy );
436508 drawNodes (g , gx , gy );
437509 g .disableScissor ();
510+
511+ // ── Edge legend (inside graph viewport, top-left corner) ──
512+ int legendX = gx + 4 ;
513+ int legendY = gy + 4 ;
514+ g .fill (legendX - 2 , legendY - 2 , legendX + 62 , legendY + 12 , 0xAA1A1E2A );
515+ g .drawString (font , "AND" , legendX , legendY , EDGE_AND , false );
516+ g .drawString (font , "OR" , legendX + 22 , legendY , EDGE_OR , false );
517+ g .drawString (font , "S" , legendX + 38 , legendY , EDGE_SINGLE , false );
518+ }
519+
520+ private void renderIdeaChipOverlay (GuiGraphics g , int sx , int sy ) {
521+ ResearchDefinition selected = getSelectedDefinition ();
522+ if (selected == null || selected .getIdeaChip ().isEmpty ()) {
523+ g .fill (sx , sy , sx + 16 , sy + 16 , 0x88000000 );
524+ } else if (!isIdeaChipSatisfied (selected )) {
525+ int x0 = sx - 1 ;
526+ int y0 = sy - 1 ;
527+ g .fill (x0 , y0 , x0 + 18 , y0 + 1 , 0xFFFF3333 );
528+ g .fill (x0 , y0 + 17 , x0 + 18 , y0 + 18 , 0xFFFF3333 );
529+ g .fill (x0 , y0 , x0 + 1 , y0 + 18 , 0xFFFF3333 );
530+ g .fill (x0 + 17 , y0 , x0 + 18 , y0 + 18 , 0xFFFF3333 );
531+ }
438532 }
439533
440534 private void drawFluidGauge (GuiGraphics g , int gx , int gy , int gw , int gh ) {
@@ -641,12 +735,81 @@ public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, doubl
641735 public void render (GuiGraphics graphics , int mouseX , int mouseY , float partialTick ) {
642736 super .render (graphics , mouseX , mouseY , partialTick );
643737
738+ // Node tooltip
644739 Optional <NodeBox > hovered = getHoveredNode (mouseX , mouseY );
645740 hovered .ifPresent (node -> renderNodeTooltip (graphics , node , mouseX , mouseY ));
646741
742+ // Fluid gauge tooltip
743+ renderFluidGaugeTooltip (graphics , mouseX , mouseY );
744+
745+ // Idea chip tooltip
746+ renderIdeaChipTooltip (graphics , mouseX , mouseY );
747+
647748 renderTooltip (graphics , mouseX , mouseY );
648749 }
649750
751+ private void renderFluidGaugeTooltip (GuiGraphics graphics , int mouseX , int mouseY ) {
752+ int gx = leftPos + ResearchTableMenu .FLUID_GAUGE_X ;
753+ int gy = topPos + ResearchTableMenu .FLUID_GAUGE_Y ;
754+ int gw = ResearchTableMenu .FLUID_GAUGE_W ;
755+ int gh = ResearchTableMenu .FLUID_GAUGE_H ;
756+
757+ if (mouseX < gx - 1 || mouseX >= gx + gw + 1 || mouseY < gy - 1 || mouseY >= gy + gh + 1 ) {
758+ return ;
759+ }
760+
761+ List <Component > tooltip = new ArrayList <>();
762+ int fluidAmount = menu .getFluidAmount ();
763+ int fluidType = menu .getFluidType ();
764+
765+ if (fluidAmount > 0 && fluidType > 0 ) {
766+ int color = ModFluids .getFluidColor (fluidType );
767+ tooltip .add (Component .literal (ModFluids .getFluidName (fluidType ))
768+ .withStyle (s -> s .withColor (color & 0x00FFFFFF )));
769+ tooltip .add (Component .literal (fluidAmount + " / " + ResearchTableBlockEntity .TANK_CAPACITY + " mB" )
770+ .withStyle (s -> s .withColor (0xBBBBBB )));
771+ } else {
772+ tooltip .add (Component .literal ("Empty" )
773+ .withStyle (s -> s .withColor (0x888888 )));
774+ tooltip .add (Component .literal ("0 / " + ResearchTableBlockEntity .TANK_CAPACITY + " mB" )
775+ .withStyle (s -> s .withColor (0xBBBBBB )));
776+ }
777+ tooltip .add (Component .literal ("Insert fluid buckets below" )
778+ .withStyle (s -> s .withColor (0x666666 ).withItalic (true )));
779+
780+ graphics .renderTooltip (font , tooltip , Optional .empty (), mouseX , mouseY );
781+ }
782+
783+ private void renderIdeaChipTooltip (GuiGraphics graphics , int mouseX , int mouseY ) {
784+ int sx = leftPos + ResearchTableMenu .IDEA_CHIP_X ;
785+ int sy = topPos + ResearchTableMenu .IDEA_CHIP_Y ;
786+
787+ if (mouseX < sx - 1 || mouseX >= sx + 17 || mouseY < sy - 1 || mouseY >= sy + 17 ) {
788+ return ;
789+ }
790+
791+ ItemStack slotStack = menu .getSlot (ResearchTableBlockEntity .SLOT_IDEA_CHIP ).getItem ();
792+ if (!slotStack .isEmpty ()) return ;
793+
794+ List <Component > tooltip = new ArrayList <>();
795+ ResearchDefinition selected = getSelectedDefinition ();
796+
797+ if (selected == null || selected .getIdeaChip ().isEmpty ()) {
798+ tooltip .add (Component .literal ("Idea Chip Slot" )
799+ .withStyle (s -> s .withColor (0x888888 )));
800+ tooltip .add (Component .literal ("No idea chip required for this research." )
801+ .withStyle (s -> s .withColor (0x666666 ).withItalic (true )));
802+ } else {
803+ ItemStack required = selected .getIdeaChip ().get ();
804+ tooltip .add (Component .literal ("Idea Chip Slot" )
805+ .withStyle (s -> s .withColor (0xFF5555 )));
806+ tooltip .add (Component .literal ("Requires: " + required .getHoverName ().getString ())
807+ .withStyle (s -> s .withColor (0xFFAAAA )));
808+ }
809+
810+ graphics .renderTooltip (font , tooltip , Optional .empty (), mouseX , mouseY );
811+ }
812+
650813 private void renderNodeTooltip (GuiGraphics graphics , NodeBox node , int mouseX , int mouseY ) {
651814 List <Component > lines = new ArrayList <>();
652815 boolean completed = menu .getCompletedResearch ().contains (node .def .getId ().toString ());
@@ -692,32 +855,13 @@ private void renderNodeTooltip(GuiGraphics graphics, NodeBox node, int mouseX, i
692855
693856 @ Override
694857 protected void renderLabels (GuiGraphics graphics , int mouseX , int mouseY ) {
695- graphics .drawString (this .font , this .title , this .titleLabelX , this .titleLabelY , 0xFF202020 , false );
858+ graphics .drawString (this .font , this .title , this .titleLabelX , this .titleLabelY , 0xFF343841 , false );
696859 graphics .drawString (this .font , this .playerInventoryTitle , this .inventoryLabelX , this .inventoryLabelY , 0xFFE6EAF5 , false );
697860
698- // Tree view controls info
699- graphics .drawString (this .font , "Tree View | scroll=zoom, R-drag=pan" ,
700- ResearchTableMenu .SEARCH_X + 84 , ResearchTableMenu .SEARCH_Y , 0xFFE5E7EB , false );
701-
702- // Edge legend
703- graphics .drawString (this .font , "AND" , GRAPH_X + GRAPH_W - 70 , ResearchTableMenu .SEARCH_Y , EDGE_AND , false );
704- graphics .drawString (this .font , "OR" , GRAPH_X + GRAPH_W - 46 , ResearchTableMenu .SEARCH_Y , EDGE_OR , false );
705- graphics .drawString (this .font , "S" , GRAPH_X + GRAPH_W - 22 , ResearchTableMenu .SEARCH_Y , EDGE_SINGLE , false );
706-
707- // Slot labels in machine panel
708- int labelY = ResearchTableMenu .LABEL_Y ;
709- graphics .drawString (this .font , "Dr" , ResearchTableMenu .DRIVE_X + 2 , labelY , 0xFFD3D7E5 , false );
710- graphics .drawString (this .font , "Cb" , ResearchTableMenu .CUBE_X + 2 , labelY , 0xFFD3D7E5 , false );
711- graphics .drawString (this .font , "Id" , ResearchTableMenu .IDEA_CHIP_X + 2 , labelY , 0xFFD3D7E5 , false );
712- graphics .drawString (this .font , "Costs" , ResearchTableMenu .COST_X , labelY , 0xFFD3D7E5 , false );
713- graphics .drawString (this .font , "Fl" , ResearchTableMenu .FLUID_GAUGE_X + 3 , labelY , 0xFFD3D7E5 , false );
714- graphics .drawString (this .font , "I/O" , ResearchTableMenu .BUCKET_IN_X , labelY , 0xFFD3D7E5 , false );
715-
861+ // Researching indicator (same as ResearchTableScreen)
716862 if (menu .isResearching ()) {
717863 graphics .drawString (this .font , "\u25CF Researching" ,
718864 ResearchTableMenu .MACHINE_PANEL_X + 4 , ResearchTableMenu .BUTTON_Y + 18 , 0xFF77DD77 , false );
719865 }
720- graphics .drawString (this .font , Math .round (zoom * 100f ) + "%" ,
721- ResearchTableMenu .SEARCH_X + 82 , ResearchTableMenu .TREE_BTN_Y , 0xFFD9DDE7 , false );
722866 }
723867}
0 commit comments