|
2 | 2 |
|
3 | 3 | import gregtech.api.GTValues; |
4 | 4 | import gregtech.api.capability.IEnergyContainer; |
| 5 | +import gregtech.api.capability.impl.AbstractRecipeLogic; |
| 6 | +import gregtech.api.mui.GTByteBufAdapters; |
| 7 | +import gregtech.api.mui.drawable.GTObjectDrawable; |
| 8 | +import gregtech.api.recipes.Recipe; |
5 | 9 | import gregtech.api.recipes.RecipeMap; |
| 10 | +import gregtech.api.recipes.chance.boost.ChanceBoostFunction; |
| 11 | +import gregtech.api.recipes.chance.output.impl.ChancedFluidOutput; |
| 12 | +import gregtech.api.recipes.chance.output.impl.ChancedItemOutput; |
| 13 | +import gregtech.api.util.FluidStackHashStrategy; |
| 14 | +import gregtech.api.util.GTUtility; |
| 15 | +import gregtech.api.util.ItemStackHashStrategy; |
6 | 16 | import gregtech.api.util.KeyUtil; |
7 | 17 | import gregtech.api.util.TextFormattingUtil; |
8 | 18 | import gregtech.common.ConfigHolder; |
9 | 19 |
|
| 20 | +import net.minecraft.item.ItemStack; |
10 | 21 | import net.minecraft.network.PacketBuffer; |
11 | 22 | import net.minecraft.util.text.TextFormatting; |
| 23 | +import net.minecraftforge.fluids.FluidStack; |
12 | 24 |
|
13 | 25 | import com.cleanroommc.modularui.api.drawable.IDrawable; |
14 | 26 | import com.cleanroommc.modularui.api.drawable.IKey; |
15 | 27 | import com.cleanroommc.modularui.api.drawable.IRichTextBuilder; |
| 28 | +import com.cleanroommc.modularui.utils.serialization.ByteBufAdapters; |
16 | 29 | import com.cleanroommc.modularui.utils.serialization.IByteBufDeserializer; |
17 | 30 | import com.cleanroommc.modularui.utils.serialization.IByteBufSerializer; |
18 | 31 | import com.cleanroommc.modularui.value.sync.PanelSyncManager; |
19 | 32 | import com.cleanroommc.modularui.value.sync.SyncHandler; |
20 | 33 | import io.netty.buffer.ByteBuf; |
21 | 34 | import io.netty.buffer.Unpooled; |
| 35 | +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenCustomHashMap; |
| 36 | +import it.unimi.dsi.fastutil.objects.Object2LongMap; |
22 | 37 | import org.jetbrains.annotations.NotNull; |
23 | 38 | import org.jetbrains.annotations.Nullable; |
24 | 39 |
|
@@ -46,6 +61,8 @@ public class MultiblockUIBuilder { |
46 | 61 | private final InternalSyncHandler syncHandler = new InternalSyncHandler(); |
47 | 62 | private final KeyManager manager = new InternalKeyManager(); |
48 | 63 |
|
| 64 | + private static final int DEFAULT_MAX_RECIPE_LINES = 25; |
| 65 | + |
49 | 66 | @Nullable |
50 | 67 | private InternalSyncer syncer; |
51 | 68 |
|
@@ -313,17 +330,23 @@ public MultiblockUIBuilder addIdlingLine(boolean checkState) { |
313 | 330 | } |
314 | 331 |
|
315 | 332 | /** |
316 | | - * Adds a simple progress line that displays progress as a percentage. |
| 333 | + * Adds a progress line that displays recipe progress as "time / total time (percentage)". |
317 | 334 | * <br> |
318 | 335 | * Added if structure is formed and the machine is active. |
319 | 336 | * |
320 | | - * @param progressPercent Progress formatted as a range of [0,1] representing the progress of the recipe. |
| 337 | + * @param progress current progress. |
| 338 | + * @param maxProgress total progress to be made. |
321 | 339 | */ |
322 | | - public MultiblockUIBuilder addProgressLine(double progressPercent) { |
| 340 | + public MultiblockUIBuilder addProgressLine(int progress, int maxProgress) { |
323 | 341 | if (!isStructureFormed || !isActive) return this; |
324 | | - addKey(KeyUtil.lang(TextFormatting.GRAY, |
325 | | - "gregtech.multiblock.progress", |
326 | | - (int) (getSyncer().syncDouble(progressPercent) * 100))); |
| 342 | + |
| 343 | + progress = getSyncer().syncInt(progress); |
| 344 | + maxProgress = getSyncer().syncInt(maxProgress); |
| 345 | + |
| 346 | + addKey(KeyUtil.lang(TextFormatting.WHITE, "gregtech.multiblock.recipe_progress", |
| 347 | + String.format("%,3.2f", (float) progress / 20), |
| 348 | + String.format("%,3.2f", (float) maxProgress / 20), |
| 349 | + String.format("%,3.1f", (float) progress / maxProgress * 100f))); |
327 | 350 | return this; |
328 | 351 | } |
329 | 352 |
|
@@ -484,6 +507,201 @@ public MultiblockUIBuilder addRecipeMapLine(RecipeMap<?> map) { |
484 | 507 | return this; |
485 | 508 | } |
486 | 509 |
|
| 510 | + /** |
| 511 | + * Adds the current outputs of a recipe from recipe logic. Items then fluids. |
| 512 | + * |
| 513 | + * @param arl an instance of an {@link AbstractRecipeLogic} to gather the outputs from. |
| 514 | + */ |
| 515 | + public MultiblockUIBuilder addRecipeOutputLine(@NotNull AbstractRecipeLogic arl) { |
| 516 | + return addRecipeOutputLine(arl, DEFAULT_MAX_RECIPE_LINES); |
| 517 | + } |
| 518 | + |
| 519 | + /** |
| 520 | + * Adds the current outputs of a recipe from recipe logic. Items then fluids. |
| 521 | + * |
| 522 | + * @param arl an instance of an {@link AbstractRecipeLogic} to gather the outputs from. |
| 523 | + * @param maxLines the maximum number of lines to print until truncating with {@code ...} |
| 524 | + */ |
| 525 | + public MultiblockUIBuilder addRecipeOutputLine(AbstractRecipeLogic arl, int maxLines) { |
| 526 | + // todo doing this every tick on the server is probably not good |
| 527 | + Recipe recipe = arl.getParallelRecipe(); |
| 528 | + |
| 529 | + if (getSyncer().syncBoolean(recipe == null)) return this; |
| 530 | + if (getSyncer().syncBoolean(arl.getRecipeMap() == null)) return this; |
| 531 | + |
| 532 | + // noinspection DataFlowIssue |
| 533 | + long eut = getSyncer().syncLong(() -> recipe.getEUt()); |
| 534 | + long maxVoltage = getSyncer().syncLong(arl.getMaximumOverclockVoltage()); |
| 535 | + |
| 536 | + List<ItemStack> itemOutputs = new ArrayList<>(); |
| 537 | + List<ChancedItemOutput> chancedItemOutputs = new ArrayList<>(); |
| 538 | + List<FluidStack> fluidOutputs = new ArrayList<>(); |
| 539 | + List<ChancedFluidOutput> chancedFluidOutputs = new ArrayList<>(); |
| 540 | + |
| 541 | + if (isServer()) { |
| 542 | + // recipe is checked indirectly for null |
| 543 | + // noinspection DataFlowIssue |
| 544 | + itemOutputs.addAll(recipe.getOutputs()); |
| 545 | + chancedItemOutputs.addAll(recipe.getChancedOutputs().getChancedEntries()); |
| 546 | + fluidOutputs.addAll(recipe.getFluidOutputs()); |
| 547 | + chancedFluidOutputs.addAll(recipe.getChancedFluidOutputs().getChancedEntries()); |
| 548 | + } |
| 549 | + |
| 550 | + itemOutputs = getSyncer().syncCollection(itemOutputs, ByteBufAdapters.ITEM_STACK); |
| 551 | + fluidOutputs = getSyncer().syncCollection(fluidOutputs, ByteBufAdapters.FLUID_STACK); |
| 552 | + chancedItemOutputs = getSyncer().syncCollection(chancedItemOutputs, GTByteBufAdapters.CHANCED_ITEM_OUTPUT); |
| 553 | + chancedFluidOutputs = getSyncer().syncCollection(chancedFluidOutputs, GTByteBufAdapters.CHANCED_FLUID_OUTPUT); |
| 554 | + |
| 555 | + addKey(KeyUtil.string(TextFormatting.GRAY, "Producing: "), Operation.NEW_LINE); |
| 556 | + |
| 557 | + var chanceFunction = arl.getRecipeMap().getChanceFunction(); |
| 558 | + int recipeTier = GTUtility.getTierByVoltage(eut); |
| 559 | + int machineTier = GTUtility.getOCTierByVoltage(maxVoltage); |
| 560 | + |
| 561 | + // items |
| 562 | + |
| 563 | + Object2LongMap<ItemStack> itemMap = new Object2LongLinkedOpenCustomHashMap<>( |
| 564 | + ItemStackHashStrategy.comparingAllButCount()); |
| 565 | + |
| 566 | + for (ItemStack stack : itemOutputs) { |
| 567 | + itemMap.merge(stack, (long) stack.getCount(), Long::sum); |
| 568 | + } |
| 569 | + |
| 570 | + for (var stack : itemMap.keySet()) { |
| 571 | + addItemOutputLine(stack, itemMap.getLong(stack), arl.getMaxProgress()); |
| 572 | + } |
| 573 | + |
| 574 | + for (var chancedItemOutput : chancedItemOutputs) { |
| 575 | + addChancedItemOutputLine(chancedItemOutput, chanceFunction, recipeTier, machineTier, arl.getMaxProgress()); |
| 576 | + } |
| 577 | + |
| 578 | + // fluids |
| 579 | + |
| 580 | + Object2LongMap<FluidStack> fluidMap = new Object2LongLinkedOpenCustomHashMap<>( |
| 581 | + FluidStackHashStrategy.comparingAllButAmount); |
| 582 | + |
| 583 | + for (FluidStack stack : fluidOutputs) { |
| 584 | + fluidMap.merge(stack, (long) stack.amount, Long::sum); |
| 585 | + } |
| 586 | + |
| 587 | + for (var stack : fluidMap.keySet()) { |
| 588 | + addFluidOutputLine(stack, fluidMap.getLong(stack), arl.getMaxProgress()); |
| 589 | + } |
| 590 | + |
| 591 | + for (var chancedFluidOutput : chancedFluidOutputs) { |
| 592 | + addChancedFluidOutputLine(chancedFluidOutput, chanceFunction, recipeTier, machineTier, |
| 593 | + arl.getMaxProgress()); |
| 594 | + } |
| 595 | + return this; |
| 596 | + } |
| 597 | + |
| 598 | + /** |
| 599 | + * Add an item output of a recipe to the display. |
| 600 | + * |
| 601 | + * @param stack the {@link ItemStack} to display. |
| 602 | + * @param recipeLength the recipe length, in ticks. |
| 603 | + */ |
| 604 | + private void addItemOutputLine(@NotNull ItemStack stack, long count, int recipeLength) { |
| 605 | + IKey name = KeyUtil.string(TextFormatting.AQUA, stack.getDisplayName()); |
| 606 | + IKey amount = KeyUtil.number(TextFormatting.GOLD, count); |
| 607 | + IKey rate = KeyUtil.string(TextFormatting.WHITE, |
| 608 | + formatRecipeRate(getSyncer().syncInt(recipeLength), count)); |
| 609 | + |
| 610 | + addKey(new GTObjectDrawable(stack, count) |
| 611 | + .asIcon() |
| 612 | + .asHoverable() |
| 613 | + .addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD); |
| 614 | + // addKey(IKey.SPACE, Operation.ADD); |
| 615 | + } |
| 616 | + |
| 617 | + /** |
| 618 | + * Add the fluid outputs of a recipe to the display. |
| 619 | + * |
| 620 | + * @param stack a {@link FluidStack}s to display. |
| 621 | + * @param recipeLength the recipe length, in ticks. |
| 622 | + */ |
| 623 | + private void addFluidOutputLine(@NotNull FluidStack stack, long count, int recipeLength) { |
| 624 | + IKey name = KeyUtil.fluid(TextFormatting.AQUA, stack); |
| 625 | + IKey amount = KeyUtil.number(TextFormatting.GOLD, count); |
| 626 | + IKey rate = KeyUtil.string(TextFormatting.WHITE, |
| 627 | + formatRecipeRate(getSyncer().syncInt(recipeLength), count)); |
| 628 | + |
| 629 | + addKey(new GTObjectDrawable(stack, count) |
| 630 | + .asIcon() |
| 631 | + .asHoverable() |
| 632 | + .addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD); |
| 633 | + // addKey(IKey.SPACE, Operation.ADD); |
| 634 | + } |
| 635 | + |
| 636 | + /** |
| 637 | + * Add a chanced item output of a recipe to the display. |
| 638 | + * |
| 639 | + * @param chancedItemOutput chanced item output |
| 640 | + * @param boostFunction function to calculate boosted chance |
| 641 | + * @param recipeTier voltage tier of the recipe |
| 642 | + * @param machineTier machine tier |
| 643 | + * @param recipeLength max duration of the recipe |
| 644 | + */ |
| 645 | + private void addChancedItemOutputLine(@NotNull ChancedItemOutput chancedItemOutput, |
| 646 | + ChanceBoostFunction boostFunction, |
| 647 | + int recipeTier, int machineTier, int recipeLength) { |
| 648 | + var stack = chancedItemOutput.getIngredient(); |
| 649 | + IKey name = KeyUtil.string(TextFormatting.AQUA, stack.getDisplayName()); |
| 650 | + IKey amount = KeyUtil.number(TextFormatting.GOLD, stack.getCount()); |
| 651 | + IKey rate = KeyUtil.string(TextFormatting.WHITE, |
| 652 | + formatRecipeRate(getSyncer().syncInt(recipeLength), stack.getCount())); |
| 653 | + |
| 654 | + addKey(new GTObjectDrawable(chancedItemOutput, stack.getCount()) |
| 655 | + .setBoostFunction(entry -> boostFunction.getBoostedChance(entry, recipeTier, machineTier)) |
| 656 | + .asIcon() |
| 657 | + .asHoverable() |
| 658 | + .addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD); |
| 659 | + // addKey(IKey.SPACE, Operation.ADD); |
| 660 | + } |
| 661 | + |
| 662 | + /** |
| 663 | + * Add a chanced fluid output of a recipe to the display. |
| 664 | + * |
| 665 | + * @param chancedFluidOutput chanced fluid output |
| 666 | + * @param boostFunction function to calculate boosted chance |
| 667 | + * @param recipeTier voltage tier of the recipe |
| 668 | + * @param machineTier machine tier |
| 669 | + * @param recipeLength max duration of the recipe |
| 670 | + */ |
| 671 | + private void addChancedFluidOutputLine(@NotNull ChancedFluidOutput chancedFluidOutput, |
| 672 | + ChanceBoostFunction boostFunction, |
| 673 | + int recipeTier, int machineTier, int recipeLength) { |
| 674 | + var stack = chancedFluidOutput.getIngredient(); |
| 675 | + IKey name = KeyUtil.fluid(TextFormatting.AQUA, stack); |
| 676 | + IKey amount = KeyUtil.number(TextFormatting.GOLD, stack.amount); |
| 677 | + IKey rate = KeyUtil.string(TextFormatting.WHITE, |
| 678 | + formatRecipeRate(getSyncer().syncInt(recipeLength), stack.amount)); |
| 679 | + |
| 680 | + addKey(new GTObjectDrawable(chancedFluidOutput, stack.amount) |
| 681 | + .setBoostFunction(entry -> boostFunction.getBoostedChance(entry, recipeTier, machineTier)) |
| 682 | + .asIcon() |
| 683 | + .asHoverable() |
| 684 | + .addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD); |
| 685 | + // addKey(IKey.SPACE, Operation.ADD); |
| 686 | + } |
| 687 | + |
| 688 | + private static String formatRecipeRate(int recipeLength, long amount) { |
| 689 | + float perSecond = ((float) amount / recipeLength) * 20f; |
| 690 | + |
| 691 | + String rate; |
| 692 | + if (perSecond > 1) { |
| 693 | + rate = "(" + String.format("%,.2f", perSecond).replaceAll("\\.?0+$", "") + "/s)"; |
| 694 | + } else { |
| 695 | + rate = "(" + String.format("%,.2f", 1 / (perSecond)).replaceAll("\\.?0+$", "") + "s/ea)"; |
| 696 | + } |
| 697 | + |
| 698 | + return rate; |
| 699 | + } |
| 700 | + |
| 701 | + private static IKey formatRecipeData(IKey name, IKey amount, IKey rate) { |
| 702 | + return IKey.comp(name, KeyUtil.string(TextFormatting.WHITE, " x "), amount, IKey.SPACE, rate); |
| 703 | + } |
| 704 | + |
487 | 705 | /** Insert an empty line into the text list. */ |
488 | 706 | public MultiblockUIBuilder addEmptyLine() { |
489 | 707 | addKey(IKey.LINE_FEED); |
|
0 commit comments