mcMMO.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. package com.gmail.nossr50;
  2. import com.gmail.nossr50.datatypes.PlayerProfile;
  3. import com.gmail.nossr50.commands.skills.*;
  4. import com.gmail.nossr50.commands.spout.*;
  5. import com.gmail.nossr50.commands.mc.*;
  6. import com.gmail.nossr50.commands.party.*;
  7. import com.gmail.nossr50.commands.general.*;
  8. import com.gmail.nossr50.config.Config;
  9. import com.gmail.nossr50.config.HiddenConfig;
  10. import com.gmail.nossr50.config.RepairConfigManager;
  11. import com.gmail.nossr50.config.SpoutConfig;
  12. import com.gmail.nossr50.config.TreasuresConfig;
  13. import com.gmail.nossr50.config.mods.CustomBlocksConfig;
  14. import com.gmail.nossr50.config.mods.CustomArmorConfig;
  15. import com.gmail.nossr50.config.mods.CustomToolsConfig;
  16. import com.gmail.nossr50.runnables.*;
  17. import com.gmail.nossr50.skills.repair.RepairManager;
  18. import com.gmail.nossr50.skills.repair.RepairManagerFactory;
  19. import com.gmail.nossr50.skills.repair.Repairable;
  20. import com.gmail.nossr50.util.Database;
  21. import com.gmail.nossr50.util.Leaderboard;
  22. import com.gmail.nossr50.util.Metrics;
  23. import com.gmail.nossr50.util.Metrics.Graph;
  24. import com.gmail.nossr50.util.Users;
  25. import com.gmail.nossr50.util.blockmeta.ChunkletManager;
  26. import com.gmail.nossr50.util.blockmeta.ChunkletManagerFactory;
  27. import com.gmail.nossr50.listeners.BlockListener;
  28. import com.gmail.nossr50.listeners.EntityListener;
  29. import com.gmail.nossr50.listeners.HardcoreListener;
  30. import com.gmail.nossr50.listeners.PlayerListener;
  31. import com.gmail.nossr50.listeners.WorldListener;
  32. import com.gmail.nossr50.locale.LocaleLoader;
  33. import net.shatteredlands.shatt.backup.ZipLibrary;
  34. import java.io.File;
  35. import java.io.IOException;
  36. import java.io.InputStream;
  37. import java.util.ArrayList;
  38. import java.util.HashMap;
  39. import java.util.List;
  40. import org.bukkit.OfflinePlayer;
  41. import org.bukkit.plugin.PluginDescriptionFile;
  42. import org.bukkit.plugin.java.JavaPlugin;
  43. import org.bukkit.plugin.PluginManager;
  44. import org.bukkit.scheduler.BukkitScheduler;
  45. import org.bukkit.configuration.file.FileConfiguration;
  46. import org.bukkit.configuration.file.YamlConfiguration;
  47. import org.bukkit.entity.Player;
  48. public class mcMMO extends JavaPlugin {
  49. private final PlayerListener playerListener = new PlayerListener(this);
  50. private final BlockListener blockListener = new BlockListener(this);
  51. private final EntityListener entityListener = new EntityListener(this);
  52. private final WorldListener worldListener = new WorldListener();
  53. private final HardcoreListener hardcoreListener = new HardcoreListener();
  54. public HashMap<String, String> aliasMap = new HashMap<String, String>(); //Alias - Command
  55. public HashMap<Integer, Player> tntTracker = new HashMap<Integer, Player>();
  56. public static File versionFile;
  57. public static Database database;
  58. public static mcMMO p;
  59. public static ChunkletManager placeStore;
  60. public static RepairManager repairManager;
  61. /* Jar Stuff */
  62. public File mcmmo;
  63. //File Paths
  64. public String mainDirectory, flatFileDirectory, usersFile, leaderboardDirectory, modDirectory;
  65. /**
  66. * Things to be run when the plugin is enabled.
  67. */
  68. public void onEnable() {
  69. p = this;
  70. setupFilePaths();
  71. //Force the loading of config files
  72. Config configInstance = Config.getInstance();
  73. TreasuresConfig.getInstance();
  74. HiddenConfig.getInstance();
  75. SpoutConfig.getInstance().load();
  76. List<Repairable> repairables = new ArrayList<Repairable>();
  77. if (configInstance.getToolModsEnabled()) {
  78. CustomToolsConfig.getInstance().load();
  79. repairables.addAll(CustomToolsConfig.getInstance().getLoadedRepairables());
  80. }
  81. if (configInstance.getArmorModsEnabled()) {
  82. CustomArmorConfig.getInstance().load();
  83. repairables.addAll(CustomArmorConfig.getInstance().getLoadedRepairables());
  84. }
  85. if (configInstance.getBlockModsEnabled()) {
  86. CustomBlocksConfig.getInstance().load();
  87. }
  88. //Load repair configs, make manager, and register them at this time
  89. RepairConfigManager rManager = new RepairConfigManager(this);
  90. repairables.addAll(rManager.getLoadedRepairables());
  91. repairManager = RepairManagerFactory.getRepairManager(repairables.size());
  92. repairManager.registerRepairables(repairables);
  93. if (!configInstance.getUseMySQL()) {
  94. Users.loadUsers();
  95. }
  96. PluginManager pm = getServer().getPluginManager();
  97. //Register events
  98. pm.registerEvents(playerListener, this);
  99. pm.registerEvents(blockListener, this);
  100. pm.registerEvents(entityListener, this);
  101. pm.registerEvents(worldListener, this);
  102. if (configInstance.getHardcoreEnabled()) {
  103. pm.registerEvents(hardcoreListener, this);
  104. }
  105. PluginDescriptionFile pdfFile = getDescription();
  106. //Setup the leaderboards
  107. if (configInstance.getUseMySQL()) {
  108. database = new Database(this);
  109. database.createStructure();
  110. }
  111. else {
  112. Leaderboard.makeLeaderboards();
  113. }
  114. for (Player player : getServer().getOnlinePlayers()) {
  115. Users.addUser(player); //In case of reload add all users back into PlayerProfile
  116. }
  117. System.out.println(pdfFile.getName() + " version " + pdfFile.getVersion() + " is enabled!" );
  118. BukkitScheduler scheduler = getServer().getScheduler();
  119. //Schedule Spout Activation 1 second after start-up
  120. scheduler.scheduleSyncDelayedTask(this, new SpoutStart(this), 20);
  121. //Periodic save timer (Saves every 10 minutes)
  122. scheduler.scheduleSyncRepeatingTask(this, new SaveTimer(this), 0, configInstance.getSaveInterval() * 1200);
  123. //Regen & Cooldown timer (Runs every second)
  124. scheduler.scheduleSyncRepeatingTask(this, new SkillMonitor(this), 0, 20);
  125. //Bleed timer (Runs every two seconds)
  126. scheduler.scheduleSyncRepeatingTask(this, new BleedTimer(), 0, 40);
  127. registerCommands();
  128. if (configInstance.getStatsTrackingEnabled()) {
  129. try {
  130. Metrics metrics = new Metrics(this);
  131. Graph graph = metrics.createGraph("Percentage of servers using timings");
  132. if (pm.useTimings()) {
  133. graph.addPlotter(new Metrics.Plotter("Enabled") {
  134. @Override
  135. public int getValue() {
  136. return 1;
  137. }
  138. });
  139. }
  140. else {
  141. graph.addPlotter(new Metrics.Plotter("Disabled") {
  142. @Override
  143. public int getValue() {
  144. return 1;
  145. }
  146. });
  147. }
  148. metrics.start();
  149. }
  150. catch (IOException e) {
  151. System.out.println("Failed to submit stats.");
  152. }
  153. }
  154. // Get our ChunkletManager
  155. placeStore = ChunkletManagerFactory.getChunkletManager();
  156. }
  157. /**
  158. * Setup the various storage file paths
  159. */
  160. public void setupFilePaths() {
  161. mcmmo = getFile();
  162. mainDirectory = getDataFolder().getPath() + File.separator;
  163. flatFileDirectory = mainDirectory + "FlatFileStuff" + File.separator;
  164. usersFile = flatFileDirectory + "mcmmo.users";
  165. leaderboardDirectory = flatFileDirectory + "Leaderboards" + File.separator;
  166. modDirectory = mainDirectory + "ModConfigs" + File.separator;
  167. }
  168. /**
  169. * Get profile of the player.
  170. * </br>
  171. * This function is designed for API usage.
  172. *
  173. * @param player Player whose profile to get
  174. * @return the PlayerProfile object
  175. */
  176. public PlayerProfile getPlayerProfile(Player player) {
  177. return Users.getProfile(player);
  178. }
  179. /**
  180. * Get profile of the player by name.
  181. * </br>
  182. * This function is designed for API usage.
  183. *
  184. * @param playerName Name of player whose profile to get
  185. * @return the PlayerProfile object
  186. */
  187. public PlayerProfile getPlayerProfileByName(String playerName) {
  188. return Users.getProfileByName(playerName);
  189. }
  190. /**
  191. * Get profile of the offline player.
  192. * </br>
  193. * This function is designed for API usage.
  194. *
  195. * @param player Offline player whose profile to get
  196. * @return the PlayerProfile object
  197. */
  198. public PlayerProfile getOfflinePlayerProfile(OfflinePlayer player) {
  199. return Users.getProfile(player);
  200. }
  201. /**
  202. * Things to be run when the plugin is disabled.
  203. */
  204. public void onDisable() {
  205. //Make sure to save player information if the server shuts down
  206. for (PlayerProfile x : Users.getProfiles().values()) {
  207. x.save();
  208. }
  209. getServer().getScheduler().cancelTasks(this); //This removes our tasks
  210. //Save our metadata
  211. placeStore.saveAll();
  212. //Cleanup empty metadata stores
  213. placeStore.cleanUp();
  214. //Remove other tasks BEFORE starting the Backup, or we just cancel it straight away.
  215. try {
  216. ZipLibrary.mcMMObackup();
  217. }
  218. catch (IOException e) {
  219. getLogger().severe(e.toString());
  220. }
  221. System.out.println("mcMMO was disabled."); //How informative!
  222. }
  223. /**
  224. * Register the commands.
  225. */
  226. private void registerCommands() {
  227. //Register aliases with the aliasmap (used in the playercommandpreprocessevent to ugly alias them to actual commands)
  228. //Skills commands
  229. aliasMap.put(LocaleLoader.getString("Acrobatics.SkillName").toLowerCase(), "acrobatics");
  230. aliasMap.put(LocaleLoader.getString("Archery.SkillName").toLowerCase(), "archery");
  231. aliasMap.put(LocaleLoader.getString("Axes.SkillName").toLowerCase(), "axes");
  232. aliasMap.put(LocaleLoader.getString("Excavation.SkillName").toLowerCase(), "excavation");
  233. aliasMap.put(LocaleLoader.getString("Fishing.SkillName").toLowerCase(), "fishing");
  234. aliasMap.put(LocaleLoader.getString("Herbalism.SkillName").toLowerCase(), "herbalism");
  235. aliasMap.put(LocaleLoader.getString("Mining.SkillName").toLowerCase(), "mining");
  236. aliasMap.put(LocaleLoader.getString("Repair.SkillName").toLowerCase(), "repair");
  237. aliasMap.put(LocaleLoader.getString("Swords.SkillName").toLowerCase(), "swords");
  238. aliasMap.put(LocaleLoader.getString("Taming.SkillName").toLowerCase(), "taming");
  239. aliasMap.put(LocaleLoader.getString("Unarmed.SkillName").toLowerCase(), "unarmed");
  240. aliasMap.put(LocaleLoader.getString("Woodcutting.SkillName").toLowerCase(), "woodcutting");
  241. //Register commands
  242. //Skills commands
  243. getCommand("acrobatics").setExecutor(new AcrobaticsCommand());
  244. getCommand("archery").setExecutor(new ArcheryCommand());
  245. getCommand("axes").setExecutor(new AxesCommand());
  246. getCommand("excavation").setExecutor(new ExcavationCommand());
  247. getCommand("fishing").setExecutor(new FishingCommand());
  248. getCommand("herbalism").setExecutor(new HerbalismCommand());
  249. getCommand("mining").setExecutor(new MiningCommand());
  250. getCommand("repair").setExecutor(new RepairCommand());
  251. getCommand("swords").setExecutor(new SwordsCommand());
  252. getCommand("taming").setExecutor(new TamingCommand());
  253. getCommand("unarmed").setExecutor(new UnarmedCommand());
  254. getCommand("woodcutting").setExecutor(new WoodcuttingCommand());
  255. Config configInstance = Config.getInstance();
  256. //mc* commands
  257. if (configInstance.getCommandMCRemoveEnabled()) {
  258. getCommand("mcremove").setExecutor(new McremoveCommand(this));
  259. }
  260. if (configInstance.getCommandMCAbilityEnabled()) {
  261. getCommand("mcability").setExecutor(new McabilityCommand());
  262. }
  263. if (configInstance.getCommandMCCEnabled()) {
  264. getCommand("mcc").setExecutor(new MccCommand());
  265. }
  266. if (configInstance.getCommandMCGodEnabled()) {
  267. getCommand("mcgod").setExecutor(new McgodCommand());
  268. }
  269. if (configInstance.getCommandmcMMOEnabled()) {
  270. getCommand("mcmmo").setExecutor(new McmmoCommand());
  271. }
  272. if (configInstance.getCommandMCRefreshEnabled()) {
  273. getCommand("mcrefresh").setExecutor(new McrefreshCommand(this));
  274. }
  275. if (configInstance.getCommandMCTopEnabled()) {
  276. getCommand("mctop").setExecutor(new MctopCommand());
  277. }
  278. if (configInstance.getCommandMCStatsEnabled()) {
  279. getCommand("mcstats").setExecutor(new McstatsCommand());
  280. }
  281. //Party commands
  282. if (configInstance.getCommandAcceptEnabled()) {
  283. getCommand("accept").setExecutor(new AcceptCommand(this));
  284. }
  285. if (configInstance.getCommandAdminChatAEnabled()) {
  286. getCommand("a").setExecutor(new ACommand(this));
  287. }
  288. if (configInstance.getCommandInviteEnabled()) {
  289. getCommand("invite").setExecutor(new InviteCommand(this));
  290. }
  291. if (configInstance.getCommandPartyEnabled()) {
  292. getCommand("party").setExecutor(new PartyCommand(this));
  293. }
  294. if (configInstance.getCommandPartyChatPEnabled()) {
  295. getCommand("p").setExecutor(new PCommand(this));
  296. }
  297. if (configInstance.getCommandPTPEnabled()) {
  298. getCommand("ptp").setExecutor(new PtpCommand(this));
  299. }
  300. //Other commands
  301. if (configInstance.getCommandAddXPEnabled()) {
  302. getCommand("addxp").setExecutor(new AddxpCommand(this));
  303. }
  304. if (configInstance.getCommandAddLevelsEnabled()) {
  305. getCommand("addlevels").setExecutor(new AddlevelsCommand(this));
  306. }
  307. if (configInstance.getCommandMmoeditEnabled()) {
  308. getCommand("mmoedit").setExecutor(new MmoeditCommand(this));
  309. }
  310. if (configInstance.getCommandInspectEnabled()) {
  311. getCommand("inspect").setExecutor(new InspectCommand(this));
  312. }
  313. if (configInstance.getCommandXPRateEnabled()) {
  314. getCommand("xprate").setExecutor(new XprateCommand(this));
  315. }
  316. getCommand("mmoupdate").setExecutor(new MmoupdateCommand(this));
  317. //Spout commands
  318. if (configInstance.getCommandXPLockEnabled()) {
  319. getCommand("xplock").setExecutor(new XplockCommand());
  320. }
  321. getCommand("mchud").setExecutor(new MchudCommand(this));
  322. }
  323. /*
  324. * Boilerplate Custom Config Stuff (Treasures)
  325. */
  326. private FileConfiguration treasuresConfig = null;
  327. private File treasuresConfigFile = null;
  328. /**
  329. * Reload the Treasures.yml file.
  330. */
  331. public void reloadTreasuresConfig() {
  332. if (treasuresConfigFile == null) {
  333. treasuresConfigFile = new File(getDataFolder(), "treasures.yml");
  334. }
  335. treasuresConfig = YamlConfiguration.loadConfiguration(treasuresConfigFile);
  336. if (isInJar("treasures.yml")) {
  337. InputStream defConfigStream = getResource("treasures.yml");
  338. YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
  339. treasuresConfig.setDefaults(defConfig);
  340. }
  341. }
  342. /**
  343. * Get the Treasures config information.
  344. *
  345. * @return the configuration object for treasures.yml
  346. */
  347. public FileConfiguration getTreasuresConfig() {
  348. if (treasuresConfig == null) {
  349. reloadTreasuresConfig();
  350. }
  351. return treasuresConfig;
  352. }
  353. /**
  354. * Save the Treasures config informtion.
  355. */
  356. public void saveTreasuresConfig() {
  357. if (treasuresConfig == null || treasuresConfigFile == null) {
  358. return;
  359. }
  360. try {
  361. treasuresConfig.save(treasuresConfigFile);
  362. }
  363. catch (IOException ex) {
  364. getLogger().severe("Could not save config to " + treasuresConfigFile + ex.toString());
  365. }
  366. }
  367. /*
  368. * Boilerplate Custom Config Stuff (Tools)
  369. */
  370. private FileConfiguration toolsConfig = null;
  371. private File toolsConfigFile = null;
  372. /**
  373. * Reload the Tools.yml file.
  374. */
  375. public void reloadToolsConfig() {
  376. if (toolsConfigFile == null) {
  377. toolsConfigFile = new File(modDirectory, "tools.yml");
  378. }
  379. toolsConfig = YamlConfiguration.loadConfiguration(toolsConfigFile);
  380. if (isInJar("tools.yml")) {
  381. InputStream defConfigStream = getResource("tools.yml");
  382. YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
  383. toolsConfig.setDefaults(defConfig);
  384. }
  385. }
  386. /**
  387. * Get the Tools config information.
  388. *
  389. * @return the configuration object for tools.yml
  390. */
  391. public FileConfiguration getToolsConfig() {
  392. if (toolsConfig == null) {
  393. reloadToolsConfig();
  394. }
  395. return toolsConfig;
  396. }
  397. /**
  398. * Save the Tools config informtion.
  399. */
  400. public void saveToolsConfig() {
  401. if (toolsConfig == null || toolsConfigFile == null) {
  402. return;
  403. }
  404. try {
  405. toolsConfig.save(toolsConfigFile);
  406. }
  407. catch (IOException ex) {
  408. getLogger().severe("Could not save config to " + toolsConfigFile + ex.toString());
  409. }
  410. }
  411. /*
  412. * Boilerplate Custom Config Stuff (Armor)
  413. */
  414. private FileConfiguration armorConfig = null;
  415. private File armorConfigFile = null;
  416. /**
  417. * Reload the Armor.yml file.
  418. */
  419. public void reloadArmorConfig() {
  420. if (armorConfigFile == null) {
  421. armorConfigFile = new File(modDirectory, "armor.yml");
  422. }
  423. armorConfig = YamlConfiguration.loadConfiguration(armorConfigFile);
  424. if (isInJar("armor.yml")) {
  425. InputStream defConfigStream = getResource("armor.yml");
  426. YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
  427. armorConfig.setDefaults(defConfig);
  428. }
  429. }
  430. /**
  431. * Get the Armor config information.
  432. *
  433. * @return the configuration object for armor.yml
  434. */
  435. public FileConfiguration getArmorConfig() {
  436. if (armorConfig == null) {
  437. reloadArmorConfig();
  438. }
  439. return armorConfig;
  440. }
  441. /**
  442. * Save the Armor config informtion.
  443. */
  444. public void saveArmorConfig() {
  445. if (armorConfig == null || armorConfigFile == null) {
  446. return;
  447. }
  448. try {
  449. armorConfig.save(armorConfigFile);
  450. }
  451. catch (IOException ex) {
  452. getLogger().severe("Could not save config to " + armorConfigFile + ex.toString());
  453. }
  454. }
  455. /*
  456. * Boilerplate Custom Config Stuff (Blocks)
  457. */
  458. private FileConfiguration blocksConfig = null;
  459. private File blocksConfigFile = null;
  460. /**
  461. * Reload the Blocks.yml file.
  462. */
  463. public void reloadBlocksConfig() {
  464. if (blocksConfigFile == null) {
  465. blocksConfigFile = new File(modDirectory, "blocks.yml");
  466. }
  467. blocksConfig = YamlConfiguration.loadConfiguration(blocksConfigFile);
  468. if (isInJar("blocks.yml")) {
  469. InputStream defConfigStream = getResource("blocks.yml");
  470. YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
  471. blocksConfig.setDefaults(defConfig);
  472. }
  473. }
  474. /**
  475. * Get the Blocks config information.
  476. *
  477. * @return the configuration object for blocks.yml
  478. */
  479. public FileConfiguration getBlocksConfig() {
  480. if (blocksConfig == null) {
  481. reloadBlocksConfig();
  482. }
  483. return blocksConfig;
  484. }
  485. /**
  486. * Save the Blocks config informtion.
  487. */
  488. public void saveBlocksConfig() {
  489. if (blocksConfig == null || blocksConfigFile == null) {
  490. return;
  491. }
  492. try {
  493. blocksConfig.save(blocksConfigFile);
  494. }
  495. catch (IOException ex) {
  496. getLogger().severe("Could not save config to " + blocksConfigFile + ex.toString());
  497. }
  498. }
  499. /*
  500. * Boilerplate Custom Config Stuff (Spout)
  501. */
  502. private FileConfiguration spoutConfig = null;
  503. private File spoutConfigFile = null;
  504. /**
  505. * Reload the Spout.yml file.
  506. */
  507. public void reloadSpoutConfig() {
  508. if (spoutConfigFile == null) {
  509. spoutConfigFile = new File(modDirectory, "spout.yml");
  510. }
  511. spoutConfig = YamlConfiguration.loadConfiguration(spoutConfigFile);
  512. if (isInJar("spout.yml")) {
  513. InputStream defConfigStream = getResource("spout.yml");
  514. YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
  515. spoutConfig.setDefaults(defConfig);
  516. }
  517. }
  518. /**
  519. * Get the Spout config information.
  520. *
  521. * @return the configuration object for spout.yml
  522. */
  523. public FileConfiguration getSpoutConfig() {
  524. if (spoutConfig == null) {
  525. reloadSpoutConfig();
  526. }
  527. return spoutConfig;
  528. }
  529. /**
  530. * Save the Spout config informtion.
  531. */
  532. public void saveSpoutConfig() {
  533. if (spoutConfig == null || spoutConfigFile == null) {
  534. return;
  535. }
  536. try {
  537. spoutConfig.save(spoutConfigFile);
  538. }
  539. catch (IOException ex) {
  540. getLogger().severe("Could not save config to " + spoutConfigFile + ex.toString());
  541. }
  542. }
  543. public boolean isInJar(String resource) {
  544. InputStream iStream = getResource(resource);
  545. return iStream != null;
  546. }
  547. }