From 35b2491f448c993c65b7403dbb330fd073898954 Mon Sep 17 00:00:00 2001 From: lordIcocain <62835225+lordIcocain@users.noreply.github.com> Date: Fri, 28 Feb 2025 21:32:34 +0200 Subject: [PATCH] Ctrl+F feature for Crafting Plan/Tree/Status (#680) Co-authored-by: Maya <10861407+serenibyss@users.noreply.github.com> --- .../gui/implementations/GuiCraftConfirm.java | 105 +++++++++++++++- .../gui/implementations/GuiCraftingCPU.java | 119 +++++++++++++++++- .../implementations/GuiCraftingStatus.java | 1 + .../client/gui/widgets/GuiCraftingTree.java | 74 ++++++++++- .../core/localization/ButtonToolTips.java | 5 +- .../appeng/core/localization/GuiColors.java | 4 +- .../appliedenergistics2/lang/en_US.lang | 5 +- .../textures/guis/craftingreport.png | Bin 1886 -> 1133 bytes .../textures/guis/craftingtree.png | Bin 1605 -> 1108 bytes .../textures/guis/searchField.png | Bin 0 -> 680 bytes 10 files changed, 304 insertions(+), 9 deletions(-) create mode 100644 src/main/resources/assets/appliedenergistics2/textures/guis/searchField.png diff --git a/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java b/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java index 80a04bd2710..7a993134015 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java +++ b/src/main/java/appeng/client/gui/implementations/GuiCraftConfirm.java @@ -41,6 +41,7 @@ import appeng.api.storage.data.IItemList; import appeng.client.gui.AEBaseGui; import appeng.client.gui.IGuiTooltipHandler; +import appeng.client.gui.widgets.GuiAeButton; import appeng.client.gui.widgets.GuiCraftingCPUTable; import appeng.client.gui.widgets.GuiCraftingTree; import appeng.client.gui.widgets.GuiImgButton; @@ -48,6 +49,7 @@ import appeng.client.gui.widgets.GuiSimpleImgButton; import appeng.client.gui.widgets.GuiTabButton; import appeng.client.gui.widgets.ICraftingCPUTableHolder; +import appeng.client.gui.widgets.MEGuiTextField; import appeng.container.implementations.ContainerCraftConfirm; import appeng.container.implementations.CraftingCPUStatus; import appeng.core.AEConfig; @@ -159,8 +161,14 @@ protected void recalculateScreenSize() { private GuiImgButton sortingModeButton; private GuiImgButton sortingDirectionButton; private GuiSimpleImgButton optimizeButton; + private GuiAeButton findNext; + private GuiAeButton findPrev; + private MEGuiTextField searchField; private int tooltip = -1; private ItemStack hoveredStack; + private ArrayList goToData = new ArrayList<>(); + private int searchGotoIndex = -1; + private IAEItemStack needHighlight; final GuiScrollbar scrollbar; @@ -300,6 +308,40 @@ public void initGui() { ButtonToolTips.OptimizePatterns.getLocal()); this.optimizeButton.enabled = false; this.buttonList.add(this.optimizeButton); + + this.searchField = new MEGuiTextField(52, 12, "Search") { + + @Override + public void onTextChange(String oldText) { + super.onTextChange(oldText); + switch (displayMode) { + case LIST -> updateSearchGoToList(); + case TREE -> craftingTree.updateSearchGoToList(this.getText().toLowerCase()); + } + } + }; + this.searchField.x = this.guiLeft + this.xSize - 101; + this.searchField.y = this.guiTop + 5; + + this.findPrev = new GuiAeButton( + 0, + this.guiLeft + this.xSize - 48, + this.guiTop + 6, + 10, + 10, + "↑", + ButtonToolTips.SearchGotoPrev.getLocal()); + this.buttonList.add(this.findPrev); + + this.findNext = new GuiAeButton( + 0, + this.guiLeft + this.xSize - 36, + this.guiTop + 6, + 10, + 10, + "↓", + ButtonToolTips.SearchGotoNext.getLocal()); + this.buttonList.add(this.findNext); } @Override @@ -423,6 +465,38 @@ public void drawFG(final int offsetX, final int offsetY, final int mouseX, final } } + private void updateSearchGoToList() { + needHighlight = null; + searchGotoIndex = -1; + goToData.clear(); + if (this.searchField.getText().isEmpty()) return; + String s = this.searchField.getText().toLowerCase(); + int visCount = 0; + for (IAEItemStack aeis : this.visual) { + if (aeis != null && Platform.getItemDisplayName(aeis).toLowerCase().contains(s)) { + goToData.add(visCount); + } + visCount++; + } + searchGoTo(true); + } + + private void searchGoTo(boolean forward) { + String s = this.searchField.getText().toLowerCase(); + if (s.isEmpty() || goToData.isEmpty()) return; + if (forward) { + searchGotoIndex++; + if (searchGotoIndex >= goToData.size()) searchGotoIndex = 0; + } else { + if (searchGotoIndex <= 0) searchGotoIndex = goToData.size(); + searchGotoIndex--; + } + + IAEItemStack aeis = this.visual.get(goToData.get(searchGotoIndex)); + this.getScrollBar().setCurrentScroll(goToData.get(searchGotoIndex) / 3 - this.rows / 2); + needHighlight = aeis.copy(); + } + private void drawListFG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { String dsp = null; @@ -607,6 +681,18 @@ private void drawListFG(final int offsetX, final int offsetY, final int mouseX, GuiColors.CraftConfirmMissingItem.getColor()); } + if (!this.searchField.getText().isEmpty() && goToData.contains(z)) { + final int startX = x * (1 + sectionLength) + xo; + final int startY = posY - 4; + final int color = needHighlight != null && needHighlight.isSameType(refStack) + ? GuiColors.SearchGoToHighlight.getColor() + : GuiColors.SearchHighlight.getColor(); + drawVerticalLine(startX, startY, startY + offY, color); + drawVerticalLine(startX + sectionLength - 1, startY, startY + offY, color); + drawHorizontalLine(startX + 1, startX + sectionLength - 2, startY + 1, color); + drawHorizontalLine(startX + 1, startX + sectionLength - 2, startY + offY - 1, color); + } + x++; if (x > 2) { @@ -683,6 +769,9 @@ public void drawBG(final int offsetX, final int offsetY, final int mouseX, final TREE_VIEW_TEXTURE_HEIGHT); } } + this.bindTexture("guis/searchField.png"); + this.drawTexturedModalRect(this.guiLeft + this.xSize - 101, this.guiTop + 5, 0, 0, 52, 12); + this.searchField.drawTextBox(); } private void setScrollBar() { @@ -870,7 +959,9 @@ protected void keyTyped(final char character, final int key) { if (key == Keyboard.KEY_RETURN || key == Keyboard.KEY_NUMPADENTER) { this.actionPerformed(this.start); } - super.keyTyped(character, key); + if (!(this.searchField.textboxKeyTyped(character, key))) { + super.keyTyped(character, key); + } } } @@ -889,6 +980,7 @@ protected void actionPerformed(final GuiButton btn) { this.displayMode = this.displayMode.next(); recalculateScreenSize(); this.setWorldAndResolution(mc, width, height); + this.searchField.setText(""); } else if (btn == this.takeScreenshot) { if (craftingTree != null) { craftingTree.saveScreenshot(); @@ -929,6 +1021,16 @@ protected void actionPerformed(final GuiButton btn) { } catch (final Throwable e) { AELog.debug(e); } + } else if (btn == this.findNext) { + switch (displayMode) { + case LIST -> searchGoTo(true); + case TREE -> craftingTree.searchGoTo(true); + } + } else if (btn == this.findPrev) { + switch (displayMode) { + case LIST -> searchGoTo(false); + case TREE -> craftingTree.searchGoTo(false); + } } } @@ -953,6 +1055,7 @@ public GuiButton getCancelButton() { protected void mouseClicked(int xCoord, int yCoord, int btn) { super.mouseClicked(xCoord, yCoord, btn); cpuTable.mouseClicked(xCoord - guiLeft, yCoord - guiTop, btn); + this.searchField.mouseClicked(xCoord, yCoord, btn); } @Override diff --git a/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java b/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java index 345eb16baf8..0aa3c9cde88 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java +++ b/src/main/java/appeng/client/gui/implementations/GuiCraftingCPU.java @@ -39,14 +39,17 @@ import appeng.api.util.DimensionalCoord; import appeng.client.gui.AEBaseGui; import appeng.client.gui.IGuiTooltipHandler; +import appeng.client.gui.widgets.GuiAeButton; import appeng.client.gui.widgets.GuiImgButton; import appeng.client.gui.widgets.GuiScrollbar; import appeng.client.gui.widgets.ISortSource; import appeng.client.gui.widgets.ITooltip; +import appeng.client.gui.widgets.MEGuiTextField; import appeng.client.render.BlockPosHighlighter; import appeng.container.implementations.ContainerCraftingCPU; import appeng.core.AEConfig; import appeng.core.AELog; +import appeng.core.localization.ButtonToolTips; import appeng.core.localization.GuiColors; import appeng.core.localization.GuiText; import appeng.core.localization.PlayerMessages; @@ -168,6 +171,12 @@ public int getStringWidth() { private final RemainingOperations remainingOperations = new RemainingOperations(); private ItemStack hoveredStack; private ItemStack hoveredNbtStack; + private GuiAeButton findNext; + private GuiAeButton findPrev; + private MEGuiTextField searchField; + private ArrayList goToData = new ArrayList<>(); + private int searchGotoIndex = -1; + private IAEItemStack needHighlight; public GuiCraftingCPU(final InventoryPlayer inventoryPlayer, final Object te) { this(new ContainerCraftingCPU(inventoryPlayer, te)); @@ -201,13 +210,17 @@ protected void actionPerformed(final GuiButton btn) { } catch (final IOException e) { AELog.debug(e); } - } - if (this.toggleHideStored == btn) { + } else if (this.toggleHideStored == btn) { this.hideStored ^= true; AEConfig.instance.getConfigManager().putSetting(Settings.HIDE_STORED, hideStored ? YesNo.YES : YesNo.NO); this.toggleHideStored.set(hideStored ? YesNo.YES : YesNo.NO); hideStoredSorting(); this.setScrollBar(); + updateSearchGoToList(true); + } else if (btn == this.findNext) { + searchGoTo(true); + } else if (btn == this.findPrev) { + searchGoTo(false); } } @@ -226,6 +239,7 @@ protected void mouseClicked(final int xCoord, final int yCoord, final int btn) { mc.thePlayer.closeScreen(); } super.mouseClicked(xCoord, yCoord, btn); + this.searchField.mouseClicked(xCoord, yCoord, btn); } @Override @@ -246,6 +260,37 @@ public void initGui() { AEConfig.instance.getConfigManager().getSetting(Settings.HIDE_STORED)); this.buttonList.add(this.toggleHideStored); this.buttonList.add(this.cancel); + + this.searchField = new MEGuiTextField(52, 12, "Search") { + + @Override + public void onTextChange(String oldText) { + super.onTextChange(oldText); + updateSearchGoToList(true); + } + }; + this.searchField.x = this.guiLeft + this.xSize - 101; + this.searchField.y = this.guiTop + 5; + + this.findPrev = new GuiAeButton( + 0, + this.guiLeft + this.xSize - 48, + this.guiTop + 6, + 10, + 10, + "↑", + ButtonToolTips.SearchGotoPrev.getLocal()); + this.buttonList.add(this.findPrev); + + this.findNext = new GuiAeButton( + 0, + this.guiLeft + this.xSize - 36, + this.guiTop + 6, + 10, + 10, + "↓", + ButtonToolTips.SearchGotoNext.getLocal()); + this.buttonList.add(this.findNext); } private void setScrollBar() { @@ -294,6 +339,47 @@ public void drawScreen(final int mouseX, final int mouseY, final float btn) { super.drawScreen(mouseX, mouseY, btn); } + private void updateSearchGoToList(boolean dropIndex) { + needHighlight = null; + goToData.clear(); + if (this.searchField.getText().isEmpty()) return; + String s = this.searchField.getText().toLowerCase(); + int visCount = 0; + for (IAEItemStack aeis : hideStored ? this.visualHiddenStored : this.visual) { + if (aeis != null && Platform.getItemDisplayName(aeis).toLowerCase().contains(s)) { + goToData.add(visCount); + } + visCount++; + } + if (dropIndex) { + searchGotoIndex = -1; + searchGoTo(true); + } + } + + private void searchGoTo(boolean forward) { + String s = this.searchField.getText().toLowerCase(); + if (s.isEmpty() || goToData.isEmpty()) return; + if (forward) { + searchGotoIndex++; + if (searchGotoIndex >= goToData.size()) searchGotoIndex = 0; + } else { + if (searchGotoIndex <= 0) searchGotoIndex = goToData.size(); + searchGotoIndex--; + } + + List visualTemp; + if (this.hideStored) { + visualTemp = this.visualHiddenStored; + } else { + visualTemp = this.visual; + } + + IAEItemStack aeis = visualTemp.get(goToData.get(searchGotoIndex)); + this.getScrollBar().setCurrentScroll(goToData.get(searchGotoIndex) / 3 - this.rows / 2); + needHighlight = aeis.copy(); + } + private void updateRemainingOperations() { int interval = 1000; if (this.remainingOperations.getRefreshTick() >= this.remainingOperations.getLastWorkingTick() + interval) { @@ -326,7 +412,7 @@ public void drawFG(final int offsetX, final int offsetY, final int mouseX, final updateRemainingOperations(); this.fontRendererObj.drawString( String.valueOf(remainingOperations.getRemainingOperations()), - TITLE_LEFT_OFFSET + 200 - this.remainingOperations.getStringWidth(), + TITLE_LEFT_OFFSET + 128 - this.remainingOperations.getStringWidth(), TITLE_TOP_OFFSET, GuiColors.CraftingCPUTitle.getColor()); @@ -473,6 +559,18 @@ public void drawFG(final int offsetX, final int offsetY, final int mouseX, final this.drawItem(posX, posY, is); + if (!this.searchField.getText().isEmpty() && goToData.contains(z)) { + final int startX = x * (1 + SECTION_LENGTH) + ITEMSTACK_LEFT_OFFSET; + final int startY = posY - 4; + final int color = needHighlight != null && needHighlight.isSameType(refStack) + ? GuiColors.SearchGoToHighlight.getColor() + : GuiColors.SearchHighlight.getColor(); + drawVerticalLine(startX, startY, startY + offY, color); + drawVerticalLine(startX + SECTION_LENGTH - 1, startY, startY + offY, color); + drawHorizontalLine(startX + 1, startX + SECTION_LENGTH - 2, startY + 1, color); + drawHorizontalLine(startX + 1, startX + SECTION_LENGTH - 2, startY + offY - 1, color); + } + x++; if (x > 2) { @@ -524,6 +622,20 @@ protected void addItemTooltip(IAEItemStack refStack, List lineList) { public void drawBG(final int offsetX, final int offsetY, final int mouseX, final int mouseY) { this.bindTexture("guis/craftingcpu.png"); this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, this.ySize); + drawSearch(); + } + + public void drawSearch() { + this.bindTexture("guis/searchField.png"); + this.drawTexturedModalRect(this.guiLeft + this.xSize - 101, this.guiTop + 5, 0, 0, 52, 12); + this.searchField.drawTextBox(); + } + + @Override + protected void keyTyped(final char character, final int key) { + if (!(this.searchField.textboxKeyTyped(character, key))) { + super.keyTyped(character, key); + } } public void postUpdate(IAEItemStack is) { @@ -565,6 +677,7 @@ public void postUpdate(final List list, final byte ref) { } if (this.hideStored) this.hideStoredSorting(); + updateSearchGoToList(false); this.setScrollBar(); } diff --git a/src/main/java/appeng/client/gui/implementations/GuiCraftingStatus.java b/src/main/java/appeng/client/gui/implementations/GuiCraftingStatus.java index c965533f79d..d9f7317563e 100644 --- a/src/main/java/appeng/client/gui/implementations/GuiCraftingStatus.java +++ b/src/main/java/appeng/client/gui/implementations/GuiCraftingStatus.java @@ -236,6 +236,7 @@ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) { this.drawTexturedModalRect(offsetX, offsetY, 0, 0, this.xSize, this.ySize); } this.cpuTable.drawBG(offsetX, offsetY); + drawSearch(); } @Override diff --git a/src/main/java/appeng/client/gui/widgets/GuiCraftingTree.java b/src/main/java/appeng/client/gui/widgets/GuiCraftingTree.java index 8d004b9f7c4..4e36b7636d3 100644 --- a/src/main/java/appeng/client/gui/widgets/GuiCraftingTree.java +++ b/src/main/java/appeng/client/gui/widgets/GuiCraftingTree.java @@ -37,6 +37,7 @@ import appeng.client.gui.AEBaseGui; import appeng.core.AELog; import appeng.core.localization.GuiColors; +import appeng.core.localization.GuiText; import appeng.crafting.v2.CraftingRequest; import appeng.crafting.v2.CraftingRequest.UsedResolverEntry; import appeng.crafting.v2.resolvers.CraftableItemResolver.CraftFromPatternTask; @@ -64,6 +65,10 @@ public class GuiCraftingTree { public static final int RESOLVER_CHILD_Y_SPACING = 12; public final int textColor = GuiColors.SearchboxText.getColor(); private static long animationFrame = System.currentTimeMillis() / 500; + private String search = ""; + private ArrayList goToData = new ArrayList(); + private int searchGotoIndex = -1; + private Node needHighlight; private abstract class Node { @@ -116,7 +121,15 @@ public RequestNode(int x, int y, Node parentNode, CraftingRequest request) { @Override public void drawImpl() { - drawSlotOutline(x, y, request.wasSimulated ? 0xCCAAAA : 0xAAAAAA, false); + int color = request.wasSimulated ? 0xCCAAAA : 0xAAAAAA; + if (!search.isEmpty() && goToData.contains(this)) { + if (needHighlight.equals(this)) { + color = GuiColors.SearchGoToHighlight.getColor(); + } else { + color = GuiColors.SearchHighlight.getColor(); + } + } + drawSlotOutline(x, y, color, false); drawStack(x, y, getDisplayItemForRequest(request), true); if (request.wasSimulated) { parent.bindTexture("guis/states.png"); @@ -148,7 +161,15 @@ public TaskNode(int x, int y, Node parentNode, UsedResolverEntry resolver) { @Override public void drawImpl() { parent.bindTexture("guis/states.png"); - drawSlotOutline(x, y, 0x777777, true); + int color = 0x777777; + if (!search.isEmpty() && goToData.contains(this)) { + if (needHighlight.equals(this)) { + color = GuiColors.SearchGoToHighlight.getColor(); + } else { + color = GuiColors.SearchHighlight.getColor(); + } + } + drawSlotOutline(x, y, color, true); List> children = null; long displayCount = resolver.resolvedStack.getStackSize(); if (resolver.task instanceof ExtractItemTask) { @@ -293,6 +314,55 @@ public void step(List stack) { } } + private String getTaskNodeDescription(final TaskNode nd) { + if (nd.resolver.task instanceof ExtractItemTask) { + return GuiText.StoredItems.getLocal(); + } else if (nd.resolver.task instanceof CraftFromPatternTask) { + return GuiText.Crafting.getLocal(); + } else if (nd.resolver.task instanceof EmitItemTask) { + return GuiText.LevelEmitter.getLocal(); + } else if (nd.resolver.task instanceof SimulateMissingItemResolver.ConjureItemTask + || nd.resolver.task instanceof IgnoreMissingItemTask) { + return GuiText.Missing.getLocal(); + } + return ""; + } + + public void updateSearchGoToList(String s) { + needHighlight = null; + searchGotoIndex = -1; + goToData.clear(); + search = s; + if (search.isEmpty()) return; + + for (ArrayList row : treeNodes.values()) { + for (Node node : row) { + if (node instanceof TaskNode tNode && getTaskNodeDescription(tNode).toLowerCase().contains(search)) { + goToData.add(node); + } else if (node instanceof RequestNode rNode + && Platform.getItemDisplayName(rNode.request.stack).toLowerCase().contains(search)) { + goToData.add(node); + } + } + } + searchGoTo(true); + } + + public void searchGoTo(boolean forward) { + if (search.isEmpty() || goToData.isEmpty()) return; + if (forward) { + searchGotoIndex++; + if (searchGotoIndex >= goToData.size()) searchGotoIndex = 0; + } else { + if (searchGotoIndex <= 0) searchGotoIndex = goToData.size(); + searchGotoIndex--; + } + final Node nd = goToData.get(searchGotoIndex); + needHighlight = nd; + scrollX = nd.x - nd.height; + scrollY = nd.y - nd.width; + } + public void setRequest(final CraftingRequest request) { final boolean isDifferent = (request != this.request); this.request = request; diff --git a/src/main/java/appeng/core/localization/ButtonToolTips.java b/src/main/java/appeng/core/localization/ButtonToolTips.java index 43798e5cfc0..0d87ade7928 100644 --- a/src/main/java/appeng/core/localization/ButtonToolTips.java +++ b/src/main/java/appeng/core/localization/ButtonToolTips.java @@ -225,7 +225,10 @@ public enum ButtonToolTips { StringOrderAlphanum, CellRestrictionLabel, - CellRestrictionHint; + CellRestrictionHint, + + SearchGotoNext, + SearchGotoPrev; private final String root; diff --git a/src/main/java/appeng/core/localization/GuiColors.java b/src/main/java/appeng/core/localization/GuiColors.java index 4398c91299e..b5b5fcb4562 100644 --- a/src/main/java/appeng/core/localization/GuiColors.java +++ b/src/main/java/appeng/core/localization/GuiColors.java @@ -150,7 +150,9 @@ public enum GuiColors { CellStatusOrange(0xFBA900), CellStatusRed(0xFB0000), CellStatusBlue(0x00AAFF), - CellStatusGreen(0x00FF00); + CellStatusGreen(0x00FF00), + SearchHighlight(0xFFFFFF55), + SearchGoToHighlight(0xFFFFAA00); private final String root; private final int color; diff --git a/src/main/resources/assets/appliedenergistics2/lang/en_US.lang b/src/main/resources/assets/appliedenergistics2/lang/en_US.lang index aa1a2e9c366..cdc677a1560 100644 --- a/src/main/resources/assets/appliedenergistics2/lang/en_US.lang +++ b/src/main/resources/assets/appliedenergistics2/lang/en_US.lang @@ -265,7 +265,7 @@ gui.appliedenergistics2.ETAFormat=HH:mm:ss gui.appliedenergistics2.SwitchCraftingSimulationDisplayMode=Switch display mode (List/Tree) gui.appliedenergistics2.Crafting=Crafting gui.appliedenergistics2.Scheduled=Scheduled -gui.appliedenergistics2.CraftingStatus=Crafting Status +gui.appliedenergistics2.CraftingStatus=Status gui.appliedenergistics2.RemainingOperations=Idle Processors gui.appliedenergistics2.FromStorage=Available gui.appliedenergistics2.FromStoragePercent=Used @@ -562,6 +562,9 @@ gui.tooltips.appliedenergistics2.StringOrderNatural=Default gui.tooltips.appliedenergistics2.StringOrderAlphanum=Natural Order gui.tooltips.appliedenergistics2.CellRestrictionLabel=Cell Restriction gui.tooltips.appliedenergistics2.CellRestrictionHint=Setup Cell Restriction + +gui.tooltips.appliedenergistics2.SearchGotoNext=Find next +gui.tooltips.appliedenergistics2.SearchGotoPrev=Find prev # Units gui.appliedenergistics2.units.appliedenergstics=AE gui.appliedenergistics2.units.ic2=Energy Units diff --git a/src/main/resources/assets/appliedenergistics2/textures/guis/craftingreport.png b/src/main/resources/assets/appliedenergistics2/textures/guis/craftingreport.png index e237b3555de21762da21be7dd9f1c3a6d47d6ac1..47e930ae8b9636edf0b30bb4c6907955ecef5b87 100644 GIT binary patch literal 1133 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6&2?&#~tz_78O`%fY(kk47* z5n0T@z_$a08M7R}Mgs-KN?apKg7ec#$`gxH8440J^GfvcQcDy}^bD+De9dHFV1DcA z;uum9_x8?N-`fll4G))WHek&;FjF{Mp)jpgr*TIL-=PSRDzx(IAN*kurUmou{_vkgd-F^xGM%7z+{g17(S?u=i;jv$H;9-FT-)6qo z0h2u$=FOXT%s9cbZ1>%dJMO=4Kdj@=bXfR+d7FHc8Y4p#3&SCTNUfgz-_`w$`FVNA zj34NRhlkJZ?(Y8hs(b&;h4HoLH2?AaS;uha{{8-N8ylOPb?et3*S^!ga`WcR^PgqR zSeUrr|B<(E-}=ul{#480(9Pg5gIsiD_co^ZxVXOX1MlzLy}LF`d&AZT^(Um>Xns#V z6;+U6O3)v86s%!wmyG28cQCTq_Gz6@BsbrhHgcT_^55162Flq7?Jcr5EWgvQnSHSF zssICrFu7>6m`qxV?hp2K-fvTW8>Z@7Y>;ANNF*EO=dsq+)Y#-{u-<$ARp#$op1=KT z59G5kM6dxJ8p+Af!pX4a!1d$*cmCvM@XLAk@T)Y#nj@3i>mP73_`SHQ|Gjud?hp2P z3=jTGwtbI}-z-F~x5y3;YG|~WcsI{{p6>N;PrusCk>P6(tx1en{I||trmZr1%0teC zA3?W2fByVf`oNxPR)3nW$G`ev!f+;Q;$ePZ<|YkfZ@gKR|DNig)Whh9@%q!8$OD$0_(KE1q@imiyf$g5B zi(^Pd+}mpxbEP?XST2-I^$H5ucX#@155w|XlX)t780T!h*6Fj4F^=hl^RyjL9PYVriP;;3_8mg5)4@xx-}UhFr>bPb3OR|{r!Rbs;Vlh zr){@>|Jlm)??DxW8^8a zdGhU7*wBn5EdJhC!zjZqSrNZmc|QZ2rCd>Ut@a>EQCJ*HVmgoC$I#c#vuFL?iSZ20 zGWpfB&{Qm3DO_rmS(KebsIY}nu>QIP)e^$ZWXD{J?pTjYJK z+V%WN**`9ZbJzHO$Q|DscmCU+JLlpV58S*b^G?$5{M$dG40GO=GR74BDV~1*#M9dK znehxiEFgxxnaA`ac;}zB*|)?Q?EHYm-+Jx`ReAl>Z~xS0u$WD-5X4qK-gwQlTpU2OhpFMGaQW5v|&PCtb0TK-tmn0vQ(rG#( zC#h0ALFXXH8;9iUTN(mZ9w~kL!rFb)y0gruPXB+YHtBuc_v5EGG2Q-B7S~?!w_RWS zwC)Di-+yh^IV*p7TqIeygK7EY%&0cL*CCV387=0YKP|gKwf%5nT>tUMTX=4A`dByI z=ds_ihnwMo5`%ymA@uEC>4Wq4*!Jw-AOEgl{hhma-{$4#PhYur?Slf{^xyU4P}z<}+;Abn)SF__T0zjmr4#CsFAtwvSe2mKhO1yFM)B3 zfm!J1l8Lit&;H4OAR=1!-^t_rUtbqEBrNgevj=8&6tF>a_g(*homEctC*m%FWISE{ KT-G@yGywqgdp!gI literal 1605 zcmdT@ZD`I>6hC_&G2LQb&0SspUbQXf<;rrM!J0 zFWKhpBuZuU!Kgo2J8c|tW0haO!lUbPn*iu42|<{y}*PdBV_i6R?* zqIlkmDvMYk5}609k_a-JO{#Xk8K`Qr^9k&|cx4q5MkeFSUP%RoEf4aFDwZF5wY;Oz z@nEJ*PdpRw2c#9Z8`Y>7a11^{Ifz%zwmnmc7TL2Xu^_QS0;j#S1|!--ZXnTK5y;|+ z70GJA;3z)bXpgWLQbw`LA&})6-wg=y^)Zs53_@V;9z=l!h^=kK1FKO01B#8-ae+S3 zw}3fw3${M)cv8Q2SAJTg%|{A`Xx;vB#nL?$YqDO~KPqpmE^?JqvZf!sG+BzS1ex*f2$A^Ybs#?kAhfl4V zvE%N8j7UaRdwKsk(Z|>a21BAzn$c35dbOmsu>Z4rofX$=x@IRWVC{I>a%*^`?BV_A zZ?Xg7Ef59>Kr)Q_M@DjZ%r6NgQv9=%KlA!4*#Gg$Zzn#T3x2uLnmMpArD|BX=IzA^ zl?Ma+x(oNUYW)cRawOC>zqk?%?+W=zi*WH~s2kl`PbZ2?4#Ca$XU;FLo)1$qlYXaE z>e{Abfv!z?eD==F*6UYGk9VXV=X>oNE~I}57H_{@d-7Ff@zaT)KTZomukra^aQ5z5 T&5@M0byCpe$#nxjIi`R($_`vp{a*phmdyDT}Q#vyLg^;WID)oBMkg;IKsN9;p$68i_CVA*F9bRT-G@yGywpDx?!>a literal 0 HcmV?d00001