RecipeViewingScreen.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /*
  2. * Roughly Enough Items by Danielshe.
  3. * Licensed under the MIT License.
  4. */
  5. package me.shedaniel.rei.gui;
  6. import com.google.common.collect.Lists;
  7. import com.mojang.blaze3d.systems.RenderSystem;
  8. import me.shedaniel.math.api.Rectangle;
  9. import me.shedaniel.math.impl.PointHelper;
  10. import me.shedaniel.rei.api.*;
  11. import me.shedaniel.rei.gui.widget.*;
  12. import me.shedaniel.rei.impl.ScreenHelper;
  13. import me.shedaniel.rei.utils.CollectionUtils;
  14. import net.minecraft.client.MinecraftClient;
  15. import net.minecraft.client.gui.Element;
  16. import net.minecraft.client.gui.screen.Screen;
  17. import net.minecraft.client.render.GuiLighting;
  18. import net.minecraft.client.resource.language.I18n;
  19. import net.minecraft.client.sound.PositionedSoundInstance;
  20. import net.minecraft.client.util.Window;
  21. import net.minecraft.sound.SoundEvents;
  22. import net.minecraft.text.LiteralText;
  23. import net.minecraft.text.TranslatableText;
  24. import net.minecraft.util.Formatting;
  25. import net.minecraft.util.Identifier;
  26. import net.minecraft.util.math.MathHelper;
  27. import java.util.*;
  28. import java.util.function.Supplier;
  29. public class RecipeViewingScreen extends Screen {
  30. public static final Identifier CHEST_GUI_TEXTURE = new Identifier("roughlyenoughitems", "textures/gui/recipecontainer.png");
  31. private static final int TABS_PER_PAGE = 5;
  32. private final List<Widget> preWidgets;
  33. private final List<Widget> widgets;
  34. private final List<TabWidget> tabs;
  35. private final Map<RecipeCategory<?>, List<RecipeDisplay>> categoriesMap;
  36. private final List<RecipeCategory<?>> categories;
  37. public int guiWidth;
  38. public int guiHeight;
  39. public int page, categoryPages;
  40. public int largestWidth, largestHeight;
  41. public boolean choosePageActivated;
  42. public RecipeChoosePageWidget recipeChoosePageWidget;
  43. private Rectangle bounds;
  44. private RecipeCategory<RecipeDisplay> selectedCategory;
  45. private ButtonWidget recipeBack, recipeNext, categoryBack, categoryNext;
  46. public RecipeViewingScreen(Map<RecipeCategory<?>, List<RecipeDisplay>> categoriesMap) {
  47. super(new LiteralText(""));
  48. this.categoryPages = 0;
  49. this.preWidgets = Lists.newArrayList();
  50. this.widgets = Lists.newArrayList();
  51. Window window = MinecraftClient.getInstance().getWindow();
  52. this.bounds = new Rectangle(window.getScaledWidth() / 2 - guiWidth / 2, window.getScaledHeight() / 2 - guiHeight / 2, 176, 186);
  53. this.categoriesMap = categoriesMap;
  54. this.categories = Lists.newArrayList();
  55. RecipeHelper.getInstance().getAllCategories().forEach(category -> {
  56. if (categoriesMap.containsKey(category))
  57. categories.add(category);
  58. });
  59. this.selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(0);
  60. this.tabs = new ArrayList<>();
  61. this.choosePageActivated = false;
  62. }
  63. @Override
  64. public boolean keyPressed(int int_1, int int_2, int int_3) {
  65. if (int_1 == 256 && choosePageActivated) {
  66. choosePageActivated = false;
  67. init();
  68. return true;
  69. }
  70. if ((int_1 == 256 || this.minecraft.options.keyInventory.matchesKey(int_1, int_2)) && this.shouldCloseOnEsc()) {
  71. MinecraftClient.getInstance().openScreen(ScreenHelper.getLastContainerScreen());
  72. ScreenHelper.getLastOverlay().init();
  73. return true;
  74. }
  75. if (int_1 == 258) {
  76. boolean boolean_1 = !hasShiftDown();
  77. if (!this.changeFocus(boolean_1))
  78. this.changeFocus(boolean_1);
  79. return true;
  80. }
  81. if (choosePageActivated)
  82. return recipeChoosePageWidget.keyPressed(int_1, int_2, int_3);
  83. else if (ClientHelper.getInstance().getNextPageKeyBinding().matchesKey(int_1, int_2)) {
  84. if (recipeNext.enabled)
  85. recipeNext.onPressed();
  86. return recipeNext.enabled;
  87. } else if (ClientHelper.getInstance().getPreviousPageKeyBinding().matchesKey(int_1, int_2)) {
  88. if (recipeBack.enabled)
  89. recipeBack.onPressed();
  90. return recipeBack.enabled;
  91. }
  92. for (Element element : children())
  93. if (element.keyPressed(int_1, int_2, int_3))
  94. return true;
  95. if (int_1 == 259) {
  96. if (ScreenHelper.hasLastRecipeScreen())
  97. minecraft.openScreen(ScreenHelper.getLastRecipeScreen());
  98. else minecraft.openScreen(ScreenHelper.getLastContainerScreen());
  99. return true;
  100. }
  101. return super.keyPressed(int_1, int_2, int_3);
  102. }
  103. @Override
  104. public boolean isPauseScreen() {
  105. return false;
  106. }
  107. @Override
  108. public void init() {
  109. super.init();
  110. this.children.clear();
  111. this.tabs.clear();
  112. this.preWidgets.clear();
  113. this.widgets.clear();
  114. this.largestWidth = width - 100;
  115. this.largestHeight = height - 40;
  116. int maxWidthDisplay = CollectionUtils.mapAndMax(getCurrentDisplayed(), display -> selectedCategory.getDisplayWidth((RecipeDisplay) display), (Comparator<Integer>) Comparator.naturalOrder()).orElse(150);
  117. this.guiWidth = MathHelper.clamp(maxWidthDisplay + 30, 0, largestWidth);
  118. this.guiHeight = MathHelper.floor(MathHelper.clamp((selectedCategory.getDisplayHeight() + 7d) * (getRecipesPerPage() + 1d) + 40d, 186d, (double) largestHeight));
  119. this.bounds = new Rectangle(width / 2 - guiWidth / 2, height / 2 - guiHeight / 2, guiWidth, guiHeight);
  120. this.page = MathHelper.clamp(page, 0, getTotalPages(selectedCategory) - 1);
  121. ButtonWidget w, w2;
  122. this.widgets.add(w = new ButtonWidget(bounds.x + 2, bounds.y - 16, 10, 10, new TranslatableText("text.rei.left_arrow")) {
  123. @Override
  124. public void onPressed() {
  125. categoryPages--;
  126. if (categoryPages < 0)
  127. categoryPages = MathHelper.ceil(categories.size() / (float) TABS_PER_PAGE) - 1;
  128. RecipeViewingScreen.this.init();
  129. }
  130. });
  131. this.widgets.add(w2 = new ButtonWidget(bounds.x + bounds.width - 12, bounds.y - 16, 10, 10, new TranslatableText("text.rei.right_arrow")) {
  132. @Override
  133. public void onPressed() {
  134. categoryPages++;
  135. if (categoryPages > MathHelper.ceil(categories.size() / (float) TABS_PER_PAGE) - 1)
  136. categoryPages = 0;
  137. RecipeViewingScreen.this.init();
  138. }
  139. });
  140. w.enabled = w2.enabled = categories.size() > TABS_PER_PAGE;
  141. widgets.add(categoryBack = new ButtonWidget(bounds.getX() + 5, bounds.getY() + 5, 12, 12, new TranslatableText("text.rei.left_arrow")) {
  142. @Override
  143. public void onPressed() {
  144. int currentCategoryIndex = categories.indexOf(selectedCategory);
  145. currentCategoryIndex--;
  146. if (currentCategoryIndex < 0)
  147. currentCategoryIndex = categories.size() - 1;
  148. selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(currentCategoryIndex);
  149. categoryPages = MathHelper.floor(currentCategoryIndex / (double) TABS_PER_PAGE);
  150. page = 0;
  151. RecipeViewingScreen.this.init();
  152. }
  153. @Override
  154. public Optional<String> getTooltips() {
  155. return Optional.ofNullable(I18n.translate("text.rei.previous_category"));
  156. }
  157. });
  158. widgets.add(new ClickableLabelWidget(bounds.getCenterX(), bounds.getY() + 7, "") {
  159. @Override
  160. public void render(int mouseX, int mouseY, float delta) {
  161. this.text = selectedCategory.getCategoryName();
  162. super.render(mouseX, mouseY, delta);
  163. }
  164. @Override
  165. public Optional<String> getTooltips() {
  166. return Optional.ofNullable(I18n.translate("text.rei.view_all_categories"));
  167. }
  168. @Override
  169. public void onLabelClicked() {
  170. MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
  171. ClientHelper.getInstance().executeViewAllRecipesKeyBind();
  172. }
  173. });
  174. widgets.add(categoryNext = new ButtonWidget(bounds.getMaxX() - 17, bounds.getY() + 5, 12, 12, new TranslatableText("text.rei.right_arrow")) {
  175. @Override
  176. public void onPressed() {
  177. int currentCategoryIndex = categories.indexOf(selectedCategory);
  178. currentCategoryIndex++;
  179. if (currentCategoryIndex >= categories.size())
  180. currentCategoryIndex = 0;
  181. selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(currentCategoryIndex);
  182. categoryPages = MathHelper.floor(currentCategoryIndex / (double) TABS_PER_PAGE);
  183. page = 0;
  184. RecipeViewingScreen.this.init();
  185. }
  186. @Override
  187. public Optional<String> getTooltips() {
  188. return Optional.ofNullable(I18n.translate("text.rei.next_category"));
  189. }
  190. });
  191. categoryBack.enabled = categories.size() > 1;
  192. categoryNext.enabled = categories.size() > 1;
  193. widgets.add(recipeBack = new ButtonWidget(bounds.getX() + 5, bounds.getY() + 21, 12, 12, new TranslatableText("text.rei.left_arrow")) {
  194. @Override
  195. public void onPressed() {
  196. page--;
  197. if (page < 0)
  198. page = getTotalPages(selectedCategory) - 1;
  199. RecipeViewingScreen.this.init();
  200. }
  201. @Override
  202. public Optional<String> getTooltips() {
  203. return Optional.ofNullable(I18n.translate("text.rei.previous_page"));
  204. }
  205. });
  206. widgets.add(new ClickableLabelWidget(bounds.getCenterX(), bounds.getY() + 23, "", categoriesMap.get(selectedCategory).size() > getRecipesPerPageByHeight()) {
  207. @Override
  208. public void render(int mouseX, int mouseY, float delta) {
  209. this.text = String.format("%d/%d", page + 1, getTotalPages(selectedCategory));
  210. super.render(mouseX, mouseY, delta);
  211. }
  212. @Override
  213. public Optional<String> getTooltips() {
  214. return Optional.ofNullable(I18n.translate("text.rei.choose_page"));
  215. }
  216. @Override
  217. public void onLabelClicked() {
  218. MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
  219. RecipeViewingScreen.this.choosePageActivated = true;
  220. RecipeViewingScreen.this.init();
  221. }
  222. });
  223. widgets.add(recipeNext = new ButtonWidget(bounds.getMaxX() - 17, bounds.getY() + 21, 12, 12, new TranslatableText("text.rei.right_arrow")) {
  224. @Override
  225. public void onPressed() {
  226. page++;
  227. if (page >= getTotalPages(selectedCategory))
  228. page = 0;
  229. RecipeViewingScreen.this.init();
  230. }
  231. @Override
  232. public Optional<String> getTooltips() {
  233. return Optional.ofNullable(I18n.translate("text.rei.next_page"));
  234. }
  235. });
  236. recipeBack.enabled = recipeNext.enabled = categoriesMap.get(selectedCategory).size() > getRecipesPerPageByHeight();
  237. for (int i = 0; i < TABS_PER_PAGE; i++) {
  238. int j = i + categoryPages * TABS_PER_PAGE;
  239. if (categories.size() > j) {
  240. TabWidget tab;
  241. tabs.add(tab = new TabWidget(i, new Rectangle(bounds.x + bounds.width / 2 - Math.min(categories.size() - categoryPages * TABS_PER_PAGE, TABS_PER_PAGE) * 14 + i * 28, bounds.y - 28, 28, 28)) {
  242. @Override
  243. public boolean mouseClicked(double mouseX, double mouseY, int button) {
  244. if (getBounds().contains(mouseX, mouseY)) {
  245. MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
  246. if (getId() + categoryPages * TABS_PER_PAGE == categories.indexOf(selectedCategory))
  247. return false;
  248. selectedCategory = (RecipeCategory<RecipeDisplay>) categories.get(getId() + categoryPages * TABS_PER_PAGE);
  249. page = 0;
  250. RecipeViewingScreen.this.init();
  251. return true;
  252. }
  253. return false;
  254. }
  255. });
  256. tab.setRenderer(categories.get(j), categories.get(j).getLogo(), categories.get(j).getCategoryName(), tab.getId() + categoryPages * TABS_PER_PAGE == categories.indexOf(selectedCategory));
  257. }
  258. }
  259. Optional<ButtonAreaSupplier> supplier = RecipeHelper.getInstance().getAutoCraftButtonArea(selectedCategory);
  260. int recipeHeight = selectedCategory.getDisplayHeight();
  261. List<RecipeDisplay> currentDisplayed = getCurrentDisplayed();
  262. for (int i = 0; i < currentDisplayed.size(); i++) {
  263. int finalI = i;
  264. final Supplier<RecipeDisplay> displaySupplier = () -> currentDisplayed.get(finalI);
  265. int displayWidth = selectedCategory.getDisplayWidth(displaySupplier.get());
  266. final Rectangle displayBounds = new Rectangle(getBounds().getCenterX() - displayWidth / 2, getBounds().y + 40 + recipeHeight * i + 7 * i, displayWidth, recipeHeight);
  267. List<Widget> setupDisplay = selectedCategory.setupDisplay(displaySupplier, displayBounds);
  268. this.widgets.addAll(setupDisplay);
  269. if (supplier.isPresent() && supplier.get().get(displayBounds) != null)
  270. this.widgets.add(new AutoCraftingButtonWidget(displayBounds, supplier.get().get(displayBounds), supplier.get().getButtonText(), displaySupplier, setupDisplay, selectedCategory));
  271. }
  272. if (choosePageActivated)
  273. recipeChoosePageWidget = new RecipeChoosePageWidget(this, page, getTotalPages(selectedCategory));
  274. else
  275. recipeChoosePageWidget = null;
  276. List<List<EntryStack>> workingStations = RecipeHelper.getInstance().getWorkingStations(selectedCategory.getIdentifier());
  277. if (!workingStations.isEmpty()) {
  278. int hh = MathHelper.floor((bounds.height - 16) / 18f);
  279. int actualHeight = Math.min(hh, workingStations.size());
  280. int innerWidth = MathHelper.ceil(workingStations.size() / ((float) hh));
  281. int xx = bounds.x - (10 + innerWidth * 18) + 6;
  282. int yy = bounds.y + 16;
  283. preWidgets.add(new CategoryBaseWidget(new Rectangle(xx - 6, yy - 6, 15 + innerWidth * 18, 11 + actualHeight * 18)));
  284. int index = 0;
  285. List<String> list = Collections.singletonList(Formatting.YELLOW.toString() + I18n.translate("text.rei.working_station"));
  286. xx += (innerWidth - 1) * 18;
  287. for (List<EntryStack> workingStation : workingStations) {
  288. preWidgets.add(EntryWidget.create(xx, yy).entries(CollectionUtils.map(workingStation, stack -> stack.copy().setting(EntryStack.Settings.TOOLTIP_APPEND_EXTRA, s -> list))));
  289. index++;
  290. yy += 18;
  291. if (index >= hh) {
  292. index = 0;
  293. yy = bounds.y + 16;
  294. xx -= 18;
  295. }
  296. }
  297. }
  298. children.addAll(tabs);
  299. children.add(ScreenHelper.getLastOverlay(true, false));
  300. children.addAll(widgets);
  301. children.addAll(preWidgets);
  302. }
  303. public List<Widget> getWidgets() {
  304. return widgets;
  305. }
  306. public List<RecipeDisplay> getCurrentDisplayed() {
  307. List<RecipeDisplay> list = Lists.newArrayList();
  308. int recipesPerPage = getRecipesPerPage();
  309. for (int i = 0; i <= recipesPerPage; i++)
  310. if (page * (recipesPerPage + 1) + i < categoriesMap.get(selectedCategory).size())
  311. list.add(categoriesMap.get(selectedCategory).get(page * (recipesPerPage + 1) + i));
  312. return list;
  313. }
  314. public RecipeCategory<?> getSelectedCategory() {
  315. return selectedCategory;
  316. }
  317. public int getPage() {
  318. return page;
  319. }
  320. public int getCategoryPage() {
  321. return categoryPages;
  322. }
  323. @SuppressWarnings("deprecation")
  324. private int getRecipesPerPage() {
  325. if (selectedCategory.getFixedRecipesPerPage() > 0)
  326. return selectedCategory.getFixedRecipesPerPage() - 1;
  327. int height = selectedCategory.getDisplayHeight();
  328. return MathHelper.clamp(MathHelper.floor(((double) largestHeight - 40d) / ((double) height + 7d)) - 1, 0, Math.min(ConfigManager.getInstance().getConfig().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
  329. }
  330. private int getRecipesPerPageByHeight() {
  331. int height = selectedCategory.getDisplayHeight();
  332. return MathHelper.clamp(MathHelper.floor(((double) guiHeight - 40d) / ((double) height + 7d)), 0, Math.min(ConfigManager.getInstance().getConfig().getMaxRecipePerPage() - 1, selectedCategory.getMaximumRecipePerPage() - 1));
  333. }
  334. @Override
  335. public void render(int mouseX, int mouseY, float delta) {
  336. this.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
  337. preWidgets.forEach(widget -> {
  338. GuiLighting.disable();
  339. widget.render(mouseX, mouseY, delta);
  340. });
  341. if (selectedCategory != null)
  342. selectedCategory.drawCategoryBackground(bounds, mouseX, mouseY, delta);
  343. else {
  344. new CategoryBaseWidget(bounds).render();
  345. if (ScreenHelper.isDarkModeEnabled()) {
  346. fill(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, 0xFF404040);
  347. fill(bounds.x + 17, bounds.y + 21, bounds.x + bounds.width - 17, bounds.y + 33, 0xFF404040);
  348. } else {
  349. fill(bounds.x + 17, bounds.y + 5, bounds.x + bounds.width - 17, bounds.y + 17, 0xFF9E9E9E);
  350. fill(bounds.x + 17, bounds.y + 21, bounds.x + bounds.width - 17, bounds.y + 33, 0xFF9E9E9E);
  351. }
  352. }
  353. for (TabWidget tab : tabs) {
  354. if (!tab.isSelected())
  355. tab.render(mouseX, mouseY, delta);
  356. }
  357. GuiLighting.disable();
  358. super.render(mouseX, mouseY, delta);
  359. widgets.forEach(widget -> {
  360. GuiLighting.disable();
  361. widget.render(mouseX, mouseY, delta);
  362. });
  363. RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
  364. GuiLighting.disable();
  365. for (TabWidget tab : tabs) {
  366. if (tab.isSelected())
  367. tab.render(mouseX, mouseY, delta);
  368. }
  369. GuiLighting.disable();
  370. ScreenHelper.getLastOverlay().render(mouseX, mouseY, delta);
  371. ScreenHelper.getLastOverlay().lateRender(mouseX, mouseY, delta);
  372. if (choosePageActivated) {
  373. setBlitOffset(500);
  374. this.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
  375. setBlitOffset(0);
  376. recipeChoosePageWidget.render(mouseX, mouseY, delta);
  377. }
  378. }
  379. public int getTotalPages(RecipeCategory category) {
  380. return MathHelper.ceil(categoriesMap.get(category).size() / (double) (getRecipesPerPage() + 1));
  381. }
  382. public Rectangle getBounds() {
  383. return bounds;
  384. }
  385. @Override
  386. public boolean charTyped(char char_1, int int_1) {
  387. if (choosePageActivated) {
  388. return recipeChoosePageWidget.charTyped(char_1, int_1);
  389. }
  390. for (Element listener : children())
  391. if (listener.charTyped(char_1, int_1))
  392. return true;
  393. return super.charTyped(char_1, int_1);
  394. }
  395. @Override
  396. public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
  397. if (choosePageActivated) {
  398. return recipeChoosePageWidget.mouseDragged(double_1, double_2, int_1, double_3, double_4);
  399. }
  400. return super.mouseDragged(double_1, double_2, int_1, double_3, double_4);
  401. }
  402. @Override
  403. public boolean mouseReleased(double double_1, double double_2, int int_1) {
  404. if (choosePageActivated) {
  405. return recipeChoosePageWidget.mouseReleased(double_1, double_2, int_1);
  406. }
  407. return super.mouseReleased(double_1, double_2, int_1);
  408. }
  409. @Override
  410. public boolean mouseScrolled(double i, double j, double amount) {
  411. for (Element listener : children())
  412. if (listener.mouseScrolled(i, j, amount))
  413. return true;
  414. if (getBounds().contains(PointHelper.fromMouse())) {
  415. if (amount > 0 && recipeBack.enabled)
  416. recipeBack.onPressed();
  417. else if (amount < 0 && recipeNext.enabled)
  418. recipeNext.onPressed();
  419. }
  420. if ((new Rectangle(bounds.x, bounds.y - 28, bounds.width, 28)).contains(PointHelper.fromMouse())) {
  421. if (amount > 0 && categoryBack.enabled)
  422. categoryBack.onPressed();
  423. else if (amount < 0 && categoryNext.enabled)
  424. categoryNext.onPressed();
  425. }
  426. return super.mouseScrolled(i, j, amount);
  427. }
  428. @Override
  429. public boolean mouseClicked(double double_1, double double_2, int int_1) {
  430. if (choosePageActivated)
  431. if (recipeChoosePageWidget.containsMouse(double_1, double_2)) {
  432. return recipeChoosePageWidget.mouseClicked(double_1, double_2, int_1);
  433. } else {
  434. choosePageActivated = false;
  435. init();
  436. return false;
  437. }
  438. for (Element entry : children())
  439. if (entry.mouseClicked(double_1, double_2, int_1)) {
  440. setFocused(entry);
  441. if (int_1 == 0)
  442. setDragging(true);
  443. return true;
  444. }
  445. return false;
  446. }
  447. @Override
  448. public Element getFocused() {
  449. if (choosePageActivated)
  450. return recipeChoosePageWidget;
  451. return super.getFocused();
  452. }
  453. }